diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/AttributeNode.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/AttributeNode.java new file mode 100644 index 000000000..b96d4d48a --- /dev/null +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/AttributeNode.java @@ -0,0 +1,25 @@ +package cz.cvut.kbss.jopa.model; + +import java.util.Map; + +/** + * Represents an attribute node of an entity graph. + * + * @param The type of the attribute + */ +public interface AttributeNode { + + /** + * Gets the name of the attribute corresponding to the attribute node. + * + * @return name of the attribute + */ + String getAttributeName(); + + /** + * Return the map of subgraphs associated with this attribute node. + * + * @return Map of subgraphs associated with this attribute node or empty Map if none have been defined + */ + Map getSubgraphs(); +} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityGraph.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityGraph.java new file mode 100644 index 000000000..c7da3a44d --- /dev/null +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityGraph.java @@ -0,0 +1,108 @@ +package cz.cvut.kbss.jopa.model; + +import cz.cvut.kbss.jopa.model.metamodel.Attribute; + +import java.util.List; + +/** + * This type represents the root of an entity graph that will be used as a template to define the attribute nodes and + * boundaries of a graph of entities and entity relationships. + *

+ * The root must be an entity type. + *

+ * The methods to add subgraphs implicitly create the corresponding attribute nodes as well; such attribute nodes should + * not be redundantly specified. + * + * @param The type of the root entity + */ +public interface EntityGraph { + + /** + * Adds one or more attribute nodes to the entity graph. + * + * @param attribute Attribute to add + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + */ + void addAttributeNodes(Attribute... attribute); + + /** + * Adds one or more attribute nodes to the entity graph. + * + * @param attributeName Name of the attribute to add + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + * @throws IllegalArgumentException If the attribute is not an attribute of this managed type + */ + void addAttributeNodes(String... attributeName); + + /** + * Adds a node to the graph that corresponds to a managed type. + *

+ * This allows for construction of multi-node entity graphs that include related managed types. + * + * @param attribute Attribute to add + * @param Attribute type + * @return Subgraph for the attribute + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + * @throws IllegalArgumentException If the attribute's target type is not a managed type + */ + Subgraph addSubgraph(Attribute attribute); + + /** + * Adds a node to the graph that corresponds to a managed type. + *

+ * This allows for construction of multi-node entity graphs that include related managed types. + * + * @param attributeName Name of the attribute to add + * @param Attribute type + * @return Subgraph for the attribute + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + * @throws IllegalArgumentException If the attribute is not an attribute of this managed type + * @throws IllegalArgumentException If the attribute's target type is not a managed type + */ + Subgraph addSubgraph(String attributeName); + + /** + * Adds a node to the graph that corresponds to a managed type with inheritance. + *

+ * This allows for multiple subclass subgraphs to be defined for this node of the entity graph. Subclass subgraphs + * will automatically include the specified attributes of superclass subgraphs. + * + * @param attribute Attribute to add + * @param cls Entity subclass + * @param Attribute type + * @return Subgraph for the attribute + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + * @throws IllegalArgumentException If the attribute's target type is not a managed type + */ + Subgraph addSubgraph(Attribute attribute, Class cls); + + /** + * Add additional attributes to this entity graph that correspond to attributes of subclasses of this EntityGraph's + * entity type. + *

+ * Subclass subgraphs will automatically include the specified attributes of superclass subgraphs. + * + * @param cls Entity subclass + * @return Subgraph for the subclass + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + * @throws IllegalArgumentException If the type is not a managed type + */ + Subgraph addSubclassSubgraph(Class cls); + + /** + * Gets the attribute nodes corresponding to the attributes of this managed type that are included in the subgraph. + * + * @return List of attribute nodes included in the subgraph or empty list if none have been defined + */ + List> getAttributeNodes(); + + /** + * Gets the name of a named EntityGraph (an entity graph defined by means of the NamedEntityGraph annotation, or + * added by means of the addNamedEntityGraph method). + *

+ * Returns null if the EntityGraph is not a named EntityGraph. + * + * @return Entity graph name, {@code null} if it is not named + */ + String getName(); +} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityManager.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityManager.java index 727efdd64..495a7d48a 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityManager.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityManager.java @@ -353,6 +353,15 @@ public interface EntityManager extends AutoCloseable { */ Query createNativeQuery(String sparqlString, String resultSetMapping); + /** + * Returns a mutable {@link EntityGraph} that can be used to dynamically create an entity graph. + * + * @param rootType Entity class + * @param Root entity class type + * @return Entity graph + */ + EntityGraph createEntityGraph(Class rootType); + /** * Return an object of the specified type to allow access to the provider-specific API. If the provider's * EntityManager implementation does not support the specified class, the {@link OWLPersistenceException} is diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/Subgraph.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/Subgraph.java new file mode 100644 index 000000000..49768d563 --- /dev/null +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/Subgraph.java @@ -0,0 +1,88 @@ +package cz.cvut.kbss.jopa.model; + +import cz.cvut.kbss.jopa.model.metamodel.Attribute; + +import java.util.List; + +/** + * This type represents a subgraph for an attribute node that corresponds to a Managed Type. + *

+ * Using this class, an entity subgraph can be embedded within an EntityGraph. + * + * @param The type of the attribute + */ +public interface Subgraph { + + /** + * Adds one or more attribute nodes to the entity graph. + * + * @param attribute Attribute to add + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + */ + void addAttributeNodes(Attribute... attribute); + + /** + * Adds one or more attribute nodes to the entity graph. + * + * @param attributeName Name of the attribute to add + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + * @throws IllegalArgumentException If the attribute is not an attribute of this managed type + */ + void addAttributeNodes(String... attributeName); + + /** + * Adds a node to the graph that corresponds to a managed type. + *

+ * This allows for construction of multi-node entity graphs that include related managed types. + * + * @param attribute Attribute to add + * @param Attribute type + * @return Subgraph for the attribute + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + * @throws IllegalArgumentException If the attribute's target type is not a managed type + */ + Subgraph addSubgraph(Attribute attribute); + + /** + * Adds a node to the graph that corresponds to a managed type. + *

+ * This allows for construction of multi-node entity graphs that include related managed types. + * + * @param attributeName Name of the attribute to add + * @param Attribute type + * @return Subgraph for the attribute + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + * @throws IllegalArgumentException If the attribute is not an attribute of this managed type + * @throws IllegalArgumentException If the attribute's target type is not a managed type + */ + Subgraph addSubgraph(String attributeName); + + /** + * Adds a node to the graph that corresponds to a managed type with inheritance. + *

+ * This allows for multiple subclass subgraphs to be defined for this node of the entity graph. Subclass subgraphs + * will automatically include the specified attributes of superclass subgraphs. + * + * @param attribute Attribute to add + * @param cls Entity subclass + * @param Attribute type + * @return Subgraph for the attribute + * @throws IllegalStateException If this {@code EntityGraph} has been statically defined + * @throws IllegalArgumentException If the attribute's target type is not a managed type + */ + Subgraph addSubgraph(Attribute attribute, Class cls); + + /** + * Gets the attribute nodes corresponding to the attributes of this managed type that are included in the subgraph. + * + * @return List of attribute nodes included in the subgraph or empty list if none have been defined + */ + List> getAttributeNodes(); + + /** + * Gets the type for which this subgraph was defined. + * + * @return managed type referenced by the subgraph + */ + Class getClassType(); +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/AbstractQuery.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/AbstractQuery.java index fa356fc76..d08ecb15a 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/AbstractQuery.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/AbstractQuery.java @@ -24,11 +24,9 @@ import cz.cvut.kbss.jopa.model.query.Query; import cz.cvut.kbss.jopa.query.QueryHolder; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; -import cz.cvut.kbss.jopa.utils.ThrowingConsumer; import cz.cvut.kbss.ontodriver.ResultSet; import cz.cvut.kbss.ontodriver.Statement; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; -import cz.cvut.kbss.ontodriver.iteration.ResultRow; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -121,7 +119,7 @@ public void executeUpdate() { } } - private Statement initQueryStatement() { + Statement initQueryStatement() { final Statement stmt = connection.createStatement(); applyQueryHints(stmt); logQuery(); @@ -265,21 +263,6 @@ void checkNumericParameter(int param, String name) { } } - /** - * Executes the query and lets the specified consumer deal with each row in the result set. - * - * @param consumer Called for every row in the result set - * @throws OntoDriverException When something goes wrong during query evaluation or result set processing - */ - void executeQuery(ThrowingConsumer consumer) throws OntoDriverException { - try (final Statement stmt = initQueryStatement()) { - final ResultSet rs = stmt.executeQuery(query.assembleQuery()); - for (ResultRow row : rs) { - consumer.accept(row); - } - } - } - private void applyQueryHints(Statement statement) { hints.forEach((hint, value) -> QueryHintsHandler.apply(hint, value, this, statement)); } @@ -287,6 +270,7 @@ private void applyQueryHints(Statement statement) { Stream executeQueryForStream(QueryResultLoader resultLoader) throws OntoDriverException { final Statement stmt = initQueryStatement(); final ResultSet rs = stmt.executeQuery(query.assembleQuery()); + resultLoader.init(rs); final Runnable closeHandler = () -> { try { stmt.close(); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/AttributeNodeImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/AttributeNodeImpl.java new file mode 100644 index 000000000..425e98a76 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/AttributeNodeImpl.java @@ -0,0 +1,40 @@ +package cz.cvut.kbss.jopa.model; + +import cz.cvut.kbss.jopa.model.metamodel.Attribute; + +import java.util.HashMap; +import java.util.Map; + +public class AttributeNodeImpl implements AttributeNode { + + private final Attribute attribute; + + private final Map subgraphs = new HashMap<>(); + + public AttributeNodeImpl(Attribute attribute) { + this.attribute = attribute; + } + + public Attribute getAttribute() { + return attribute; + } + + @Override + public String getAttributeName() { + return attribute.getName(); + } + + @Override + public Map getSubgraphs() { + return subgraphs; + } + + void addSubgraph(EntityGraphImpl graph) { + subgraphs.put(graph.getClassType(), graph); + } + + @Override + public String toString() { + return attribute.toString(); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityGraphImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityGraphImpl.java new file mode 100644 index 000000000..8fa818d3b --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityGraphImpl.java @@ -0,0 +1,95 @@ +package cz.cvut.kbss.jopa.model; + +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.ManagedType; +import cz.cvut.kbss.jopa.model.metamodel.Metamodel; +import cz.cvut.kbss.jopa.model.metamodel.PluralAttribute; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class EntityGraphImpl implements EntityGraph, Subgraph { + + private final String name; + + private final ManagedType entityType; + private final Metamodel metamodel; + + private final Map> attributeNodes = new HashMap<>(); + + public EntityGraphImpl(ManagedType entityType, Metamodel metamodel) { + this.entityType = entityType; + this.metamodel = metamodel; + this.name = null; + } + + public EntityGraphImpl(String name, ManagedType entityType, Metamodel metamodel) { + this.name = name; + this.entityType = entityType; + this.metamodel = metamodel; + } + + @Override + public void addAttributeNodes(Attribute... attribute) { + for (Attribute att : attribute) { + attributeNodes.put(att.getName(), new AttributeNodeImpl<>(att)); + } + } + + @Override + public void addAttributeNodes(String... attributeName) { + for (String attName : attributeName) { + final Attribute att = entityType.getAttribute(attName); + assert att != null; + attributeNodes.put(attName, new AttributeNodeImpl<>(att)); + } + } + + @Override + public EntityGraphImpl addSubgraph(Attribute attribute) { + final AttributeNodeImpl node = (AttributeNodeImpl) attributeNodes.computeIfAbsent(attribute.getName(), k -> new AttributeNodeImpl<>(attribute)); + Class javaType = attribute.getJavaType(); + if (attribute.isCollection()) { + javaType = ((PluralAttribute) attribute).getBindableJavaType(); + } + final EntityGraphImpl g = new EntityGraphImpl(metamodel.entity(javaType), metamodel); + node.addSubgraph(g); + return g; + } + + @Override + public Subgraph addSubgraph(String attributeName) { + final Attribute att = (Attribute) entityType.getAttribute(attributeName); + return addSubgraph(att); + } + + @Override + public Subgraph addSubgraph(Attribute attribute, Class cls) { + final EntityGraphImpl g = addSubgraph(attribute); + g.addSubclassSubgraph(cls); + return g; + } + + @Override + public Subgraph addSubclassSubgraph(Class cls) { + // TODO + return null; + } + + @Override + public List> getAttributeNodes() { + return new ArrayList<>(attributeNodes.values()); + } + + @Override + public String getName() { + return name; + } + + @Override + public Class getClassType() { + return entityType.getJavaType(); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerImpl.java index f75a83ece..1c3783542 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerImpl.java @@ -570,6 +570,12 @@ public CriteriaBuilder getCriteriaBuilder() { return getCurrentPersistenceContext().getCriteriaBuilder(); } + @Override + public EntityGraph createEntityGraph(Class rootType) { + ensureOpen(); + return getCurrentPersistenceContext().createEntityGraph(rootType); + } + @Override public T unwrap(Class cls) { ensureOpen(); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryHintsHandler.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryHintsHandler.java index 2e2053793..00db8dc74 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryHintsHandler.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryHintsHandler.java @@ -97,6 +97,7 @@ abstract static class Hint { registerHint(new DisableInferenceHint()); registerHint(new TargetOntologyHint()); registerHint(new OptimizedQueryResultEntityLoadingHint()); + registerHint(new FetchGraphHint()); } Hint(String name, Object defaultValue) { @@ -225,4 +226,15 @@ void applyToQuery(Object hintValue, AbstractQuery query, Statement statement) { // Do nothing, the optimizer is enabled as soon as the hint is set } } + + protected static class FetchGraphHint extends Hint { + public FetchGraphHint() { + super(QueryHints.FETCH_GRAPH, null); + } + + @Override + void applyToQuery(Object hintValue, AbstractQuery query, Statement statement) { + // Do nothing, the query pulls the fetch graph from the hint automatically + } + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryImpl.java index bead95ffc..c11ac3055 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryImpl.java @@ -23,6 +23,8 @@ import cz.cvut.kbss.jopa.model.query.Query; import cz.cvut.kbss.jopa.query.QueryHolder; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; +import cz.cvut.kbss.ontodriver.ResultSet; +import cz.cvut.kbss.ontodriver.Statement; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.iteration.ResultRow; @@ -57,7 +59,12 @@ public List getResultList() { private List getResultListImpl() throws OntoDriverException { final List res = new ArrayList<>(); - executeQuery(rs -> res.add(extractRow(rs))); + try (final Statement stmt = initQueryStatement()) { + final ResultSet rs = stmt.executeQuery(query.assembleQuery()); + for (ResultRow row : rs) { + res.add(extractRow(row)); + } + } return res; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryResultLoader.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryResultLoader.java index d5edc3948..7cd972b94 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryResultLoader.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/QueryResultLoader.java @@ -17,6 +17,8 @@ */ package cz.cvut.kbss.jopa.model; +import cz.cvut.kbss.jopa.query.sparql.loader.SparqlAssemblyModifierVisitor; +import cz.cvut.kbss.ontodriver.ResultSet; import cz.cvut.kbss.ontodriver.iteration.ResultRow; import java.util.Optional; @@ -26,7 +28,11 @@ * * @param Type of the result */ -public interface QueryResultLoader { +public interface QueryResultLoader extends SparqlAssemblyModifierVisitor { + + default void init(ResultSet resultSet) { + // Do nothing by default + } /** * Loads the result from the given result row. diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/TypedQueryImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/TypedQueryImpl.java index 3012eacf5..5ec7ad528 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/TypedQueryImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/TypedQueryImpl.java @@ -25,9 +25,12 @@ import cz.cvut.kbss.jopa.model.query.TypedQuery; import cz.cvut.kbss.jopa.query.QueryHints; import cz.cvut.kbss.jopa.query.QueryHolder; -import cz.cvut.kbss.jopa.query.sparql.loader.QueryResultLoadingOptimizer; +import cz.cvut.kbss.jopa.query.sparql.loader.SparqlQueryResultLoadingOptimizer; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; +import cz.cvut.kbss.ontodriver.ResultSet; +import cz.cvut.kbss.ontodriver.Statement; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; +import cz.cvut.kbss.ontodriver.iteration.ResultRow; import java.util.ArrayList; import java.util.List; @@ -38,12 +41,12 @@ public class TypedQueryImpl extends AbstractQuery implements TypedQuery { private final Class resultType; - private final QueryResultLoadingOptimizer queryResultLoadingOptimizer; + private final SparqlQueryResultLoadingOptimizer queryResultLoadingOptimizer; private Descriptor descriptor = new EntityDescriptor(); public TypedQueryImpl(QueryHolder query, Class resultType, ConnectionWrapper connection, - QueryResultLoadingOptimizer queryResultLoadingOptimizer) { + SparqlQueryResultLoadingOptimizer queryResultLoadingOptimizer) { super(query, connection); this.resultType = Objects.requireNonNull(resultType); this.queryResultLoadingOptimizer = queryResultLoadingOptimizer; @@ -65,14 +68,30 @@ public List getResultList() { private List getResultListImpl() throws OntoDriverException { final List res = new ArrayList<>(); - queryResultLoadingOptimizer.optimizeQueryAssembly(resultType, descriptor); - final QueryResultLoader resultLoader = queryResultLoadingOptimizer.getQueryResultLoader(resultType, descriptor); - - executeQuery(rr -> resultLoader.loadResult(rr).ifPresent(res::add)); + final EntityGraph fetchGraph = getFetchGraph(); + queryResultLoadingOptimizer.modifyQueryAssembly(resultType, descriptor, fetchGraph); + final QueryResultLoader resultLoader = queryResultLoadingOptimizer.getQueryResultLoader(resultType, descriptor, fetchGraph); + + try (final Statement stmt = initQueryStatement()) { + final ResultSet rs = stmt.executeQuery(query.assembleQuery()); + resultLoader.init(rs); + for (ResultRow row : rs) { + resultLoader.loadResult(row).ifPresent(res::add); + } + } resultLoader.loadLastPending().ifPresent(res::add); return res; } + @SuppressWarnings("unchecked") + private EntityGraph getFetchGraph() { + final Object fetchGraph = getHints().get(QueryHints.FETCH_GRAPH); + if (fetchGraph != null && !(fetchGraph instanceof EntityGraph)) { + throw new IllegalArgumentException("Fetch graph query hint value must be an instance of " + EntityGraph.class.getName()); + } + return (EntityGraph) fetchGraph; + } + public Descriptor getDescriptor() { return descriptor; } @@ -102,8 +121,9 @@ public X getSingleResult() { @Override public Stream getResultStream() { - queryResultLoadingOptimizer.optimizeQueryAssembly(resultType, descriptor); - final QueryResultLoader resultLoader = queryResultLoadingOptimizer.getQueryResultLoader(resultType, descriptor); + final EntityGraph fetchGraph = getFetchGraph(); + queryResultLoadingOptimizer.modifyQueryAssembly(resultType, descriptor, fetchGraph); + final QueryResultLoader resultLoader = queryResultLoadingOptimizer.getQueryResultLoader(resultType, descriptor, fetchGraph); try { return executeQueryForStream(resultLoader); } catch (OntoDriverException e) { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractIdentifiableType.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractIdentifiableType.java index 64aadb6f2..765cb14b0 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractIdentifiableType.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractIdentifiableType.java @@ -285,6 +285,27 @@ public boolean hasVersionAttribute() { }); } + public AbstractAttribute getAttributeIncludingSubTypes(String name) { + return getDeclaredAttributeImpl(name).orElseGet(() -> { + final AbstractAttribute subAtt = recursivelyGetDeclaredAttribute(name); + if (subAtt != null) { + return subAtt; + } + if (classSupertype != null) { + return classSupertype.getAttribute(name, javaType); + } + throw attributeMissing(name, false); + }); + } + + private AbstractAttribute recursivelyGetDeclaredAttribute(String name) { + return getDeclaredAttributeImpl(name).orElseGet(() -> (AbstractAttribute) subtypes.stream() + .map(ait -> ait.recursivelyGetDeclaredAttribute(name)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null)); + } + /** * Gets attribute with the specified name. *

diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityConstructor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityConstructor.java index 080180c81..5949f13f0 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityConstructor.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityConstructor.java @@ -34,9 +34,11 @@ import cz.cvut.kbss.jopa.oom.query.PluralQueryAttributeStrategy; import cz.cvut.kbss.jopa.oom.query.QueryFieldStrategy; import cz.cvut.kbss.jopa.oom.query.SingularQueryAttributeStrategy; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator; import cz.cvut.kbss.jopa.utils.CollectionFactory; @@ -54,7 +56,6 @@ import java.util.Set; import static cz.cvut.kbss.jopa.model.metamodel.AbstractQueryAttribute.THIS_PARAMETER; -import static cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator.isNotLazy; class EntityConstructor { @@ -87,12 +88,14 @@ T reconstructEntity(EntityConstructionParameters constructionParams, Coll } final T instance = createEntityInstance(constructionParams.id(), et); mapper.registerInstance(constructionParams.id(), instance); - final LoadStateDescriptor loadStateDescriptor = LoadStateDescriptorFactory.createAllUnknown(instance, et); + final LoadStateDescriptor loadStateDescriptor = constructionParams.fetchGraph().isPresent() + ? LoadStateDescriptorFactory.createForFetchGraph(instance, et, constructionParams.fetchGraph()) + : LoadStateDescriptorFactory.createAllUnknown(instance, et); loadStateRegistry.put(instance, loadStateDescriptor); populateAttributes(instance, constructionParams, axioms, loadStateDescriptor); populateQueryAttributes(instance, et, loadStateDescriptor); processEmptyAttributes(instance, et, loadStateDescriptor); - validateIntegrityConstraints(instance, et); + validateIntegrityConstraints(instance, et, loadStateDescriptor); return instance; } @@ -126,14 +129,14 @@ private void populateAttributes(final T instance, EntityConstructionParamete continue; } final FieldStrategy, T> fs = getFieldLoader( - ax, attributes, fieldLoaders, et, constructionParams.descriptor()); + ax, attributes, fieldLoaders, constructionParams); if (fs == null) { if (!MappingUtils.isClassAssertion(ax)) { LOG.trace("No attribute found for property {}. Axiom {} will be skipped.", ax.getAssertion(), ax); } continue; } - if (fs.attribute.getFetchType() == FetchType.LAZY && !constructionParams.forceEager()) { + if (isLazilyLoaded(fs.attribute, constructionParams)) { fs.lazilyAddAxiomValue(ax); } else { fs.addAxiomValue(ax); @@ -142,7 +145,7 @@ private void populateAttributes(final T instance, EntityConstructionParamete // We need to build the field values separately because some may be plural, and we have to wait until all values are prepared for (FieldStrategy, ?> fs : fieldLoaders.values()) { fs.buildInstanceFieldValue(instance); - if (fs.attribute.getFetchType() == FetchType.LAZY && !constructionParams.forceEager() && fs.hasValue()) { + if (isLazilyLoaded(fs.attribute, constructionParams) && fs.hasValue()) { loadStateDescriptor.setLoaded((FieldSpecification) fs.attribute, LoadState.NOT_LOADED); } else { loadStateDescriptor.setLoaded((FieldSpecification) fs.attribute, LoadState.LOADED); @@ -165,22 +168,27 @@ private void populateAttributes(final T instance, EntityConstructionParamete Axiom ax, Map> attributes, Map, FieldStrategy, T>> loaders, - EntityType et, Descriptor desc) { + EntityConstructor.EntityConstructionParameters constructionParams) { final URI attId = ax.getAssertion().getIdentifier(); FieldSpecification att = attributes.get(attId); if (att == null) { - if (et.getProperties() != null) { - att = et.getProperties(); + if (constructionParams.entityType().getProperties() != null) { + att = constructionParams.entityType().getProperties(); } else { return null; } } if (!loaders.containsKey(att)) { - loaders.put(att, FieldStrategy.createFieldStrategy(et, att, desc, mapper)); + loaders.put(att, FieldStrategy.createFieldStrategy(constructionParams.entityType(), att, new ObjectGraphInfo(constructionParams.descriptor(), constructionParams.fetchGraph()), mapper)); } return loaders.get(att); } + private boolean isLazilyLoaded(FieldSpecification att, EntityConstructionParameters constructionParams) { + return att.getFetchType() == FetchType.LAZY && !constructionParams.fetchGraph() + .hasAttribute(att) && !constructionParams.forceEager(); + } + /** * Populate all query based attributes in the given instance. * @@ -274,7 +282,7 @@ private static void setAttributeQueryParameters(T instance, QueryAttribute void processEmptyAttributes(T entity, EntityType et, LoadStateDescriptor loadStateDescriptor) { et.getFieldSpecifications().stream() .filter(fs -> { - final Object value= EntityPropertiesUtils.getFieldValue(fs.getJavaField(), entity); + final Object value = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), entity); return value == null || (value instanceof Collection && ((Collection) value).isEmpty()); }) .forEach(fs -> { @@ -291,11 +299,13 @@ private void processEmptyAttributes(T entity, EntityType et, LoadStateDes }); } - private void validateIntegrityConstraints(T entity, EntityType et) { + private void validateIntegrityConstraints(T entity, EntityType et, + LoadStateDescriptor loadStateDescriptor) { if (shouldSkipICValidationOnLoad()) { return; } - IntegrityConstraintsValidator.getValidator().validate(entity, et, isNotLazy()); + IntegrityConstraintsValidator.getValidator() + .validate(entity, et, fs -> loadStateDescriptor.isLoaded(fs) == LoadState.LOADED); } private boolean shouldSkipICValidationOnLoad() { @@ -315,7 +325,7 @@ void validateIntegrityConstraints(T entity, FieldSpecification void setFieldValue(T entity, FieldSpecification fieldSpec, Collection> axioms, EntityType et, Descriptor entityDescriptor) { final FieldStrategy, T> fs = FieldStrategy - .createFieldStrategy(et, fieldSpec, entityDescriptor, mapper); + .createFieldStrategy(et, fieldSpec, new ObjectGraphInfo(entityDescriptor), mapper); axioms.forEach(fs::addAxiomValue); fs.buildInstanceFieldValue(entity); validateIntegrityConstraints(entity, fieldSpec, et); @@ -332,9 +342,10 @@ void setQueryAttributeFieldValue(T entity, QueryAttribute quer * @param id Entity identifier * @param entityType Entity type * @param descriptor Entity descriptor, specifies e.g., repository contexts + * @param fetchGraph Entity fetch graph * @param forceEager Whether all attributes have to be loaded eagerly * @param Entity type */ record EntityConstructionParameters(URI id, IdentifiableEntityType entityType, Descriptor descriptor, - boolean forceEager) {} + FetchGraphWrapper fetchGraph, boolean forceEager) {} } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityDeconstructor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityDeconstructor.java index 1115e6f79..f15001568 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityDeconstructor.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityDeconstructor.java @@ -22,6 +22,7 @@ import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.QueryAttribute; import cz.cvut.kbss.jopa.oom.exception.EntityDeconstructionException; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.ontodriver.model.NamedResource; import java.net.URI; @@ -66,7 +67,7 @@ private void addAssertions(T entity, EntityType et, FieldSpecification fieldSpec, Descriptor entityDescriptor, final AxiomValueGatherer valueBuilder) { final FieldStrategy, T> fs = FieldStrategy - .createFieldStrategy(et, fieldSpec, entityDescriptor, mapper); + .createFieldStrategy(et, fieldSpec, new ObjectGraphInfo(entityDescriptor), mapper); fs.setReferenceSavingResolver(referenceSavingResolver); fs.buildAxiomValuesFromInstance(entity, valueBuilder); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityInstanceLoader.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityInstanceLoader.java index b07938cf5..52053ce25 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityInstanceLoader.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityInstanceLoader.java @@ -127,7 +127,7 @@ U reconstructEntityFromAxioms(LoadingParameters loadingParam } } return axioms.isEmpty() ? null : entityBuilder.reconstructEntity( - new EntityConstructor.EntityConstructionParameters<>(loadingParameters.identifier(), et, loadingParameters.descriptor(), loadingParameters.forceEager()), + new EntityConstructor.EntityConstructionParameters<>(loadingParameters.identifier(), et, loadingParameters.descriptor(), loadingParameters.fetchGraph(), loadingParameters.forceEager()), axioms); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityMappingHelper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityMappingHelper.java index 766bc200e..05817b7b8 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityMappingHelper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityMappingHelper.java @@ -17,8 +17,8 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.sessions.ConfigurationHolder; import cz.cvut.kbss.ontodriver.descriptor.ContainerDescriptor; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; @@ -31,12 +31,10 @@ interface EntityMappingHelper extends ConfigurationHolder { - T getEntityFromCacheOrOntology(Class cls, URI identifier, Descriptor descriptor); + T getEntityFromCacheOrOntology(Class cls, URI identifier, ObjectGraphInfo objectGraphInfo); EntityType getEntityType(Class cls); - boolean isManagedType(Class cls); - URI generateIdentifier(EntityType et); T getOriginalInstance(T clone); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/FieldStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/FieldStrategy.java index a16937df9..4d8e6826e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/FieldStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/FieldStrategy.java @@ -32,6 +32,7 @@ import cz.cvut.kbss.jopa.model.metamodel.RdfContainerAttributeImpl; import cz.cvut.kbss.jopa.model.metamodel.SingularAttribute; import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; @@ -45,6 +46,8 @@ import java.util.stream.Collectors; /** + * Handles actual mapping of a persistent field to/from axioms. + * * @param The attribute specification type, e.g. {@link SingularAttribute}, {@link ListAttribute} * @param Entity class */ @@ -67,16 +70,16 @@ abstract class FieldStrategy, X> { static FieldStrategy, X> createFieldStrategy(EntityType et, FieldSpecification att, - Descriptor entityDescriptor, + ObjectGraphInfo objectInfo, EntityMappingHelper mapper) { if (att.equals(et.getIdentifier())) { - return new IdentifierFieldStrategy<>(et, (Identifier) att, entityDescriptor, mapper); + return new IdentifierFieldStrategy<>(et, (Identifier) att, objectInfo.descriptor(), mapper); } if (att instanceof TypesSpecification) { - return new TypesFieldStrategy<>(et, (TypesSpecification) att, entityDescriptor, mapper); + return new TypesFieldStrategy<>(et, (TypesSpecification) att, objectInfo.descriptor(), mapper); } else if (att instanceof PropertiesSpecification) { return new PropertiesFieldStrategy<>(et, (PropertiesSpecification) att, - entityDescriptor, mapper); + objectInfo.descriptor(), mapper); } final AbstractAttribute attribute = (AbstractAttribute) att; if (attribute.isCollection()) { @@ -84,24 +87,24 @@ abstract class FieldStrategy, X> { case ANNOTATION: return createPluralAnnotationPropertyStrategy(et, (AbstractPluralAttribute) attribute, - entityDescriptor, mapper); + objectInfo.descriptor(), mapper); case DATA: return createPluralDataPropertyStrategy(et, (AbstractPluralAttribute) attribute, - entityDescriptor, mapper); + objectInfo.descriptor(), mapper); case OBJECT: return createPluralObjectPropertyStrategy(et, (AbstractPluralAttribute) attribute, - entityDescriptor, mapper); + objectInfo, mapper); default: break; } } else { switch (attribute.getPersistentAttributeType()) { case ANNOTATION: - return createSingularAnnotationPropertyStrategy(et, attribute, entityDescriptor, mapper); + return createSingularAnnotationPropertyStrategy(et, attribute, objectInfo.descriptor(), mapper); case DATA: - return createSingularDataPropertyStrategy(et, attribute, entityDescriptor, mapper); + return createSingularDataPropertyStrategy(et, attribute, objectInfo.descriptor(), mapper); case OBJECT: - return new SingularObjectPropertyStrategy<>(et, attribute, entityDescriptor, mapper); + return new SingularObjectPropertyStrategy<>(et, attribute, objectInfo, mapper); default: break; } @@ -129,7 +132,7 @@ abstract class FieldStrategy, X> { return new RdfContainerDataPropertyStrategy<>(et, (RdfContainerAttributeImpl) attribute, descriptor, mapper); } if (attribute.getCollectionType() == CollectionType.LIST) { - return createListPropertyStrategy(et, (ListAttributeImpl) attribute, descriptor, mapper); + return createListPropertyStrategy(et, (ListAttributeImpl) attribute, new ObjectGraphInfo(descriptor), mapper); } if (MultilingualString.class.equals(attribute.getElementType().getJavaType())) { return new PluralMultilingualStringFieldStrategy<>(et, @@ -141,32 +144,32 @@ abstract class FieldStrategy, X> { } private static FieldStrategy, Y> createPluralObjectPropertyStrategy( - EntityType et, AbstractPluralAttribute attribute, Descriptor descriptor, + EntityType et, AbstractPluralAttribute attribute, ObjectGraphInfo objectGraphInfo, EntityMappingHelper mapper) { if (attribute.isRdfContainer()) { - return new RdfContainerObjectPropertyStrategy<>(et, (RdfContainerAttributeImpl) attribute, descriptor, mapper); + return new RdfContainerObjectPropertyStrategy<>(et, (RdfContainerAttributeImpl) attribute, objectGraphInfo, mapper); } return switch (attribute.getCollectionType()) { - case LIST -> createListPropertyStrategy(et, (ListAttributeImpl) attribute, descriptor, + case LIST -> createListPropertyStrategy(et, (ListAttributeImpl) attribute, objectGraphInfo, mapper); - case COLLECTION, SET -> new SimpleSetPropertyStrategy<>(et, attribute, descriptor, mapper); + case COLLECTION, SET -> new SimpleSetPropertyStrategy<>(et, attribute, objectGraphInfo, mapper); default -> throw new UnsupportedOperationException( "Unsupported plural attribute collection type " + attribute.getCollectionType()); }; } private static FieldStrategy, Y> createListPropertyStrategy( - EntityType et, ListAttributeImpl attribute, Descriptor descriptor, + EntityType et, ListAttributeImpl attribute, ObjectGraphInfo objectGraphInfo, EntityMappingHelper mapper) { switch (attribute.getSequenceType()) { case referenced: if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.OBJECT) { - return new ReferencedListPropertyStrategy<>(et, attribute, descriptor, mapper); + return new ReferencedListPropertyStrategy<>(et, attribute, objectGraphInfo, mapper); } else { - return new ReferencedListDataPropertyStrategy<>(et, attribute, descriptor, mapper); + return new ReferencedListDataPropertyStrategy<>(et, attribute, objectGraphInfo.descriptor(), mapper); } case simple: - return new SimpleListPropertyStrategy<>(et, attribute, descriptor, mapper); + return new SimpleListPropertyStrategy<>(et, attribute, objectGraphInfo, mapper); default: throw new UnsupportedOperationException( "Unsupported list attribute sequence type " + attribute.getSequenceType()); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ListPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ListPropertyStrategy.java index 97ca5b1ea..c00a41582 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ListPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ListPropertyStrategy.java @@ -17,9 +17,9 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.ListAttributeImpl; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.jopa.utils.IdentifierTransformer; import cz.cvut.kbss.ontodriver.descriptor.ListDescriptor; @@ -34,9 +34,9 @@ abstract class ListPropertyStrategy extends PluralObjectPropertyStrategy, X> { - ListPropertyStrategy(EntityType et, ListAttributeImpl att, Descriptor descriptor, + ListPropertyStrategy(EntityType et, ListAttributeImpl att, ObjectGraphInfo objectGraphInfo, EntityMappingHelper mapper) { - super(et, att, descriptor, mapper); + super(et, att, objectGraphInfo, mapper); } @Override diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperImpl.java index 534f70809..dbefe25c0 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperImpl.java @@ -27,12 +27,14 @@ import cz.cvut.kbss.jopa.oom.exception.EntityDeconstructionException; import cz.cvut.kbss.jopa.oom.exception.EntityReconstructionException; import cz.cvut.kbss.jopa.oom.exception.UnpersistedChangeException; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.sessions.AbstractUnitOfWork; import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.jopa.sessions.cache.CacheManager; import cz.cvut.kbss.jopa.sessions.cache.Descriptors; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingParameters; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; @@ -58,6 +60,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -75,14 +78,14 @@ public class ObjectOntologyMapperImpl implements ObjectOntologyMapper, EntityMap private final AxiomDescriptorFactory descriptorFactory; private final EntityConstructor entityBuilder; private final EntityDeconstructor entityBreaker; - private Map instanceRegistry; private final PendingReferenceRegistry pendingReferences; + private Map instanceRegistry; + private Map>> axiomsPerEntity = Map.of(); + private final EntityInstanceLoader defaultInstanceLoader; private final EntityInstanceLoader twoStepInstanceLoader; - private final EntityReferenceFactory referenceFactory; - public ObjectOntologyMapperImpl(AbstractUnitOfWork uow, Connection connection) { this.uow = Objects.requireNonNull(uow); this.storageConnection = Objects.requireNonNull(connection); @@ -102,7 +105,6 @@ public ObjectOntologyMapperImpl(AbstractUnitOfWork uow, Connection connection) { .descriptorFactory(descriptorFactory) .entityBuilder(entityBuilder).cache(getCache()) .loadStateRegistry(uow.getLoadStateRegistry()).build(); - this.referenceFactory = new EntityReferenceFactory(uow.getMetamodel(), uow); } private CacheManager getCache() { @@ -163,16 +165,26 @@ public T loadEntity(AxiomBasedLoadingParameters loadingParameters) { if (loadingParameters.axioms().isEmpty()) { return null; } - final URI identifier = loadingParameters.axioms().iterator().next().getSubject().getIdentifier(); - final IdentifiableEntityType et = getEntityType(loadingParameters.cls()); - final LoadingParameters params = new LoadingParameters<>(loadingParameters.cls(), identifier, loadingParameters.descriptor(), false, loadingParameters.bypassCache()); + this.axiomsPerEntity = new HashMap<>(); + loadingParameters.axioms().forEach(ax -> axiomsPerEntity.computeIfAbsent(ax.getSubject() + .getIdentifier(), k -> new HashSet<>()) + .add(ax)); + final LoadingParameters params = new LoadingParameters<>(loadingParameters.cls(), + loadingParameters.config().subject(), + loadingParameters.config().descriptor(), new FetchGraphWrapper(loadingParameters.config() + .fetchGraph()), false, loadingParameters.bypassCache()); + return loadEntityFromAxioms(params); + } + + private T loadEntityFromAxioms(LoadingParameters params) { + final IdentifiableEntityType et = getEntityType(params.entityClass()); final T result; if (et.hasSubtypes()) { - result = twoStepInstanceLoader.loadEntityFromAxioms(params, loadingParameters.axioms()); + result = twoStepInstanceLoader.loadEntityFromAxioms(params, axiomsPerEntity.get(params.identifier())); } else { - result = defaultInstanceLoader.loadEntityFromAxioms(params, loadingParameters.axioms()); + result = defaultInstanceLoader.loadEntityFromAxioms(params, axiomsPerEntity.get(params.identifier())); } - if (result != null) { + if (result != null && !params.fetchGraph().isPresent()) { cacheLoadedEntity(params, result); } return result; @@ -182,7 +194,7 @@ public T loadEntity(AxiomBasedLoadingParameters loadingParameters) { public T getReference(LoadingParameters loadingParameters) { assert loadingParameters != null; - return referenceFactory.createReferenceProxy(loadingParameters); + return new EntityReferenceFactory(uow.getMetamodel(), uow).createReferenceProxy(loadingParameters); } @Override @@ -190,11 +202,6 @@ public IdentifiableEntityType getEntityType(Class cls) { return uow.getMetamodel().entity(cls); } - @Override - public boolean isManagedType(Class cls) { - return uow.isEntityType(cls); - } - @Override public void loadFieldValue(T entity, FieldSpecification fieldSpec, Descriptor descriptor) { assert entity != null; @@ -281,12 +288,12 @@ private void persistPendingReferences(T instance, NamedResource identifier) } @Override - public T getEntityFromCacheOrOntology(Class cls, URI identifier, Descriptor descriptor) { - final T orig = uow.getManagedOriginal(cls, identifier, descriptor); + public T getEntityFromCacheOrOntology(Class cls, URI identifier, ObjectGraphInfo objectGraphInfo) { + final T orig = uow.getManagedOriginal(cls, identifier, objectGraphInfo.descriptor()); if (orig != null) { return orig; } - return defaultInstanceLoader.loadCached(getEntityType(cls), identifier, descriptor).orElseGet(() -> { + return defaultInstanceLoader.loadCached(getEntityType(cls), identifier, objectGraphInfo.descriptor()).orElseGet(() -> { if (instanceRegistry.containsKey(identifier)) { final Object existing = instanceRegistry.get(identifier); if (!cls.isAssignableFrom(existing.getClass())) { @@ -296,14 +303,14 @@ public T getEntityFromCacheOrOntology(Class cls, URI identifier, Descript return cls.cast(existing); } else { // setup loading params - LoadingParameters params = initEntityLoadingParameters(cls, identifier, descriptor); - return loadEntityInternal(params); + LoadingParameters params = initEntityLoadingParameters(cls, identifier, objectGraphInfo); + return axiomsPerEntity.containsKey(identifier) ? loadEntityFromAxioms(params) : loadEntityInternal(params); } }); } - private LoadingParameters initEntityLoadingParameters(Class cls, URI identifier, Descriptor descriptor) { - return new LoadingParameters<>(cls, identifier, descriptor, false, uow.isReadOnly()); + private LoadingParameters initEntityLoadingParameters(Class cls, URI identifier, ObjectGraphInfo objectGraphInfo) { + return new LoadingParameters<>(cls, identifier, objectGraphInfo.descriptor(), objectGraphInfo.fetchGraph(), false, uow.isReadOnly()); } @Override @@ -407,7 +414,8 @@ public Configuration getConfiguration() { public Set> getAttributeAxioms(T entity, FieldSpecification fieldSpec, Descriptor entityDescriptor) { final EntityType et = (EntityType) getEntityType(entity.getClass()); - return FieldStrategy.createFieldStrategy(et, fieldSpec, entityDescriptor, this).buildAxiomsFromInstance(entity); + return FieldStrategy.createFieldStrategy(et, fieldSpec, new ObjectGraphInfo(entityDescriptor), this) + .buildAxiomsFromInstance(entity); } @Override @@ -424,7 +432,7 @@ public boolean isInferred(Axiom axiom, URI context) { public boolean isInferred(T entity, FieldSpecification fieldSpec, Object value, Descriptor entityDescriptor) { final EntityType et = (EntityType) getEntityType(entity.getClass()); - final FieldStrategy fs = FieldStrategy.createFieldStrategy(et, fieldSpec, entityDescriptor, this); + final FieldStrategy fs = FieldStrategy.createFieldStrategy(et, fieldSpec, new ObjectGraphInfo(entityDescriptor), this); final Collection> values = fs.toAxiomValue(value); final Set contexts = entityDescriptor.getAttributeContexts(fieldSpec); final NamedResource subject = NamedResource.create(EntityPropertiesUtils.getIdentifier(entity, et)); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralObjectPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralObjectPropertyStrategy.java index eafad92b5..71709ee70 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralObjectPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralObjectPropertyStrategy.java @@ -20,6 +20,8 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.AbstractPluralAttribute; import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.utils.CollectionFactory; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.jopa.utils.IdentifierTransformer; @@ -44,12 +46,15 @@ abstract class PluralObjectPropertyStrategy values; private boolean hasLazyValues = false; - PluralObjectPropertyStrategy(EntityType et, Y att, Descriptor descriptor, EntityMappingHelper mapper) { - super(et, att, descriptor, mapper); + PluralObjectPropertyStrategy(EntityType et, Y att, ObjectGraphInfo objectGraphInfo, EntityMappingHelper mapper) { + super(et, att, objectGraphInfo.descriptor(), mapper); + this.fetchGraph = objectGraphInfo.fetchGraph(); this.values = CollectionFactory.createDefaultCollection(att.getCollectionType()); } @@ -66,7 +71,7 @@ void addAxiomValue(Axiom ax) { // with the 'else' branch behavior? values.add(attribute.getConverter().convertToAttribute(valueIdentifier)); } else { - final Object value = mapper.getEntityFromCacheOrOntology(elementType, valueIdentifier.getIdentifier(), getDescriptorForRetrieval()); + final Object value = mapper.getEntityFromCacheOrOntology(elementType, valueIdentifier.getIdentifier(), propagateObjectGraphInfo()); if (value != null) { values.add(value); } else { @@ -80,6 +85,10 @@ private Descriptor getDescriptorForRetrieval() { return entityDescriptor.getAttributeDescriptor(attribute).unwrap(); } + private ObjectGraphInfo propagateObjectGraphInfo() { + return new ObjectGraphInfo(getDescriptorForRetrieval(), fetchGraph.getAttributeSubgraph(attribute)); + } + @Override void lazilyAddAxiomValue(Axiom ax) { final Class elementType = attribute.getBindableJavaType(); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/RdfContainerObjectPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/RdfContainerObjectPropertyStrategy.java index 96e02cb37..6e67d6652 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/RdfContainerObjectPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/RdfContainerObjectPropertyStrategy.java @@ -18,9 +18,9 @@ package cz.cvut.kbss.jopa.oom; import cz.cvut.kbss.jopa.model.annotations.RDFContainerType; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.RdfContainerAttributeImpl; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.jopa.utils.IdentifierTransformer; import cz.cvut.kbss.ontodriver.descriptor.ContainerDescriptor; @@ -39,8 +39,8 @@ class RdfContainerObjectPropertyStrategy extends PluralObjectPropertyStrategy private final RDFContainerType containerType; RdfContainerObjectPropertyStrategy(EntityType et, RdfContainerAttributeImpl att, - Descriptor descriptor, EntityMappingHelper mapper) { - super(et, att, descriptor, mapper); + ObjectGraphInfo objectGraphInfo, EntityMappingHelper mapper) { + super(et, att, objectGraphInfo, mapper); this.containerType = att.getContainerType(); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ReferencedListPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ReferencedListPropertyStrategy.java index 4759c36f2..8637f7237 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ReferencedListPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ReferencedListPropertyStrategy.java @@ -17,9 +17,9 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.ListAttributeImpl; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListValueDescriptor; @@ -32,9 +32,9 @@ class ReferencedListPropertyStrategy extends ListPropertyStrategy, X> { - ReferencedListPropertyStrategy(EntityType et, ListAttributeImpl att, Descriptor descriptor, + ReferencedListPropertyStrategy(EntityType et, ListAttributeImpl att, ObjectGraphInfo objectGraphInfo, EntityMappingHelper mapper) { - super(et, att, descriptor, mapper); + super(et, att, objectGraphInfo, mapper); } @Override diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SimpleListPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SimpleListPropertyStrategy.java index 99697d0d2..a7b2ee824 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SimpleListPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SimpleListPropertyStrategy.java @@ -17,9 +17,9 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.ListAttributeImpl; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptor; import cz.cvut.kbss.ontodriver.descriptor.SimpleListValueDescriptor; import cz.cvut.kbss.ontodriver.model.Axiom; @@ -31,9 +31,9 @@ class SimpleListPropertyStrategy extends ListPropertyStrategy { - SimpleListPropertyStrategy(EntityType et, ListAttributeImpl att, Descriptor descriptor, + SimpleListPropertyStrategy(EntityType et, ListAttributeImpl att, ObjectGraphInfo objectGraphInfo, EntityMappingHelper mapper) { - super(et, att, descriptor, mapper); + super(et, att, objectGraphInfo, mapper); } @Override diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SimpleSetPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SimpleSetPropertyStrategy.java index db54da028..9fe244d04 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SimpleSetPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SimpleSetPropertyStrategy.java @@ -17,9 +17,9 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.AbstractPluralAttribute; import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.jopa.utils.IdentifierTransformer; import cz.cvut.kbss.ontodriver.model.NamedResource; @@ -33,9 +33,9 @@ class SimpleSetPropertyStrategy extends PluralObjectPropertyStrategy, X> { - SimpleSetPropertyStrategy(EntityType et, AbstractPluralAttribute att, Descriptor descriptor, - EntityMappingHelper mapper) { - super(et, att, descriptor, mapper); + SimpleSetPropertyStrategy(EntityType et, AbstractPluralAttribute att, + ObjectGraphInfo objectGraphInfo, EntityMappingHelper mapper) { + super(et, att, objectGraphInfo, mapper); } @Override diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularObjectPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularObjectPropertyStrategy.java index 3246b47b0..e89e8f636 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularObjectPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularObjectPropertyStrategy.java @@ -18,12 +18,17 @@ package cz.cvut.kbss.jopa.oom; import cz.cvut.kbss.jopa.exceptions.CardinalityConstraintViolatedException; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.AbstractAttribute; import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.jopa.utils.IdentifierTransformer; -import cz.cvut.kbss.ontodriver.model.*; +import cz.cvut.kbss.ontodriver.model.Assertion; +import cz.cvut.kbss.ontodriver.model.Axiom; +import cz.cvut.kbss.ontodriver.model.AxiomImpl; +import cz.cvut.kbss.ontodriver.model.NamedResource; +import cz.cvut.kbss.ontodriver.model.Value; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,11 +41,14 @@ class SingularObjectPropertyStrategy extends FieldStrategy et, AbstractAttribute att, - Descriptor descriptor, EntityMappingHelper mapper) { - super(et, att, descriptor, mapper); + ObjectGraphInfo objectGraphInfo, EntityMappingHelper mapper) { + super(et, att, objectGraphInfo.descriptor(), mapper); + this.fetchGraph = objectGraphInfo.fetchGraph(); } @Override @@ -57,8 +65,7 @@ void addAxiomValue(Axiom ax) { // with the 'else' branch behavior? newValue = attribute.getConverter().convertToAttribute(valueIdentifier); } else { - newValue = mapper.getEntityFromCacheOrOntology(targetType, valueIdentifier.getIdentifier(), - entityDescriptor.getAttributeDescriptor(attribute)); + newValue = mapper.getEntityFromCacheOrOntology(targetType, valueIdentifier.getIdentifier(), propagateGraphInfo()); if (newValue == null) { LOG.trace("Value of axiom {} could not be loaded as entity filling attribute {}.", ax, attribute); return; @@ -68,6 +75,10 @@ void addAxiomValue(Axiom ax) { this.value = newValue; } + private ObjectGraphInfo propagateGraphInfo() { + return new ObjectGraphInfo(entityDescriptor.getAttributeDescriptor(attribute), fetchGraph.getAttributeSubgraph(attribute)); + } + private void verifyCardinality(NamedResource subject) { if (value != null) { throw new CardinalityConstraintViolatedException( diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/util/ObjectGraphInfo.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/util/ObjectGraphInfo.java new file mode 100644 index 000000000..476aad0e3 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/util/ObjectGraphInfo.java @@ -0,0 +1,11 @@ +package cz.cvut.kbss.jopa.oom.util; + +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; + +public record ObjectGraphInfo(Descriptor descriptor, FetchGraphWrapper fetchGraph) { + + public ObjectGraphInfo(Descriptor descriptor) { + this(descriptor, new FetchGraphWrapper()); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGenerator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGenerator.java index d952cafbf..c5f8bbe9e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGenerator.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGenerator.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.proxy.lazy.gen; import cz.cvut.kbss.jopa.exception.LazyLoadingException; +import cz.cvut.kbss.jopa.model.LoadState; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.gen.PersistenceContextAwareClassGenerator; import cz.cvut.kbss.jopa.model.metamodel.gen.PersistentPropertySetterMatcher; @@ -155,7 +156,7 @@ private ProxyMethodsInterceptor() { } public static boolean isLoaded(@This LazyLoadingEntityProxy proxy, @FieldValue("value") T value) { - return value != null; + return value != null || proxy.getPersistenceContext().isLoaded(proxy, proxy.getFieldSpec()) == LoadState.LOADED; } public static T getLoadedValue(@This LazyLoadingEntityProxy proxy, @FieldValue("value") T value) { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryHints.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryHints.java index 4f59dfdd9..250b0ae6f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryHints.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryHints.java @@ -50,6 +50,14 @@ public class QueryHints { */ public static final String ENABLE_ENTITY_LOADING_OPTIMIZER = "cz.cvut.kbss.jopa.query.enableEntityLoadingOptimizer"; + /** + * Allows specifying the graph to be used for fetching the entities. + *

+ * A {@literal fetch graph} specifies the attributes that the query should load. All other attributes are loaded + * lazily. + */ + public static final String FETCH_GRAPH = "cz.cvut.kbss.jopa.query.fetchGraph"; + private QueryHints() { throw new AssertionError(); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/QueryAttributes.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/QueryAttributes.java index 92fb1b728..adf10a15b 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/QueryAttributes.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/QueryAttributes.java @@ -20,5 +20,5 @@ import cz.cvut.kbss.jopa.query.QueryType; import org.antlr.v4.runtime.Token; -public record QueryAttributes(QueryType queryType, boolean hasOffset, boolean hasLimit, boolean hasGraphOrService, Token lastClosingCurlyBraceToken) { +public record QueryAttributes(QueryType queryType, boolean hasOffset, boolean hasLimit, boolean hasOrderBy, boolean hasGraphOrService, Token lastClosingCurlyBraceToken) { } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/Sparql11QueryListener.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/Sparql11QueryListener.java index b31d4c38e..1fd7c7cba 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/Sparql11QueryListener.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/Sparql11QueryListener.java @@ -35,6 +35,7 @@ public class Sparql11QueryListener extends SparqlParserBaseListener { private boolean inProjection; private boolean hasLimit; private boolean hasOffset; + private boolean hasOrderBy; private boolean hasGraphOrService; private int depth = 0; @@ -112,6 +113,11 @@ public void enterOffsetClause(SparqlParser.OffsetClauseContext ctx) { this.hasOffset = true; } + @Override + public void enterOrderClause(SparqlParser.OrderClauseContext ctx) { + this.hasOrderBy = true; + } + @Override public void enterGraphGraphPattern(SparqlParser.GraphGraphPatternContext ctx) { this.hasGraphOrService = true; @@ -156,7 +162,7 @@ public void enterVar(SparqlParser.VarContext ctx) { } QueryAttributes getQueryAttributes() { - return new QueryAttributes(queryType, hasOffset, hasLimit, hasGraphOrService, lastClosingCurlyBrace); + return new QueryAttributes(queryType, hasOffset, hasLimit, hasOrderBy, hasGraphOrService, lastClosingCurlyBrace); } List> getParameters() { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryFactory.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryFactory.java index cac977ab4..1d7a74f2b 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryFactory.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryFactory.java @@ -24,7 +24,6 @@ import cz.cvut.kbss.jopa.query.mapper.SparqlResultMapper; import cz.cvut.kbss.jopa.query.parameter.ParameterValueFactory; import cz.cvut.kbss.jopa.query.soql.SoqlQueryParser; -import cz.cvut.kbss.jopa.query.sparql.loader.QueryResultLoadingOptimizer; import cz.cvut.kbss.jopa.query.sparql.loader.SparqlQueryResultLoadingOptimizer; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; import cz.cvut.kbss.jopa.sessions.UnitOfWork; @@ -81,8 +80,7 @@ public TypedQueryImpl createNativeQuery(String sparql, Class resultCla private TypedQueryImpl createQueryImpl(String query, Class resultClass, QueryParser parser) { Objects.requireNonNull(resultClass); final TokenStreamSparqlQueryHolder queryHolder = (TokenStreamSparqlQueryHolder) parser.parseQuery(query, resultClass); - final QueryResultLoadingOptimizer - queryResultLoadingOptimizer = new SparqlQueryResultLoadingOptimizer(queryHolder, uow, connection); + final SparqlQueryResultLoadingOptimizer queryResultLoadingOptimizer = new SparqlQueryResultLoadingOptimizer(queryHolder, uow, connection); return new TypedQueryImpl<>(queryHolder, resultClass, connection, queryResultLoadingOptimizer); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoader.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoader.java index 30537411f..63c8e6d73 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoader.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoader.java @@ -19,13 +19,16 @@ import cz.cvut.kbss.jopa.exceptions.CardinalityConstraintViolatedException; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.model.EntityGraph; import cz.cvut.kbss.jopa.model.QueryResultLoader; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.Attribute; -import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; +import cz.cvut.kbss.ontodriver.ResultSet; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.iteration.ResultRow; import cz.cvut.kbss.ontodriver.model.Assertion; @@ -37,9 +40,15 @@ import org.slf4j.LoggerFactory; import java.net.URI; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Loads query results as axioms and then uses {@link cz.cvut.kbss.jopa.sessions.UnitOfWork} to create the actual entity @@ -51,7 +60,7 @@ *

    *
  • Query result contains at least one column
  • *
  • The first column contains the subject URI
  • - *
  • The remaining columns contain attribute values. Each column is named as {@code subjectName + attributeName}
  • + *
  • The remaining columns contain attribute values. Each column is named as {@code subjectName + "_" + attributeName}
  • *
*

* If the loader is unable to load the entity (e.g., due to cardinality constraint violation), it falls back to @@ -66,16 +75,64 @@ class AttributeBasedRowsToAxiomsQueryResultLoader implements QueryResultLoade private final UnitOfWork uow; private final Class resultType; private final Descriptor descriptor; - private final EntityType entityType; + private final IdentifiableEntityType entityType; + private final EntityGraph fetchGraph; + private final SparqlAssemblyModifier queryAssemblyModifier; + + private List mappings; private Set> currentEntityAxioms = Set.of(); private NamedResource currentSubject; - AttributeBasedRowsToAxiomsQueryResultLoader(UnitOfWork uow, Class resultType, Descriptor descriptor) { + AttributeBasedRowsToAxiomsQueryResultLoader(UnitOfWork uow, Class resultType, Descriptor descriptor, + EntityGraph fetchGraph) { + this(uow, resultType, descriptor, fetchGraph, new NoopSparqlAssemblyModifier()); + } + + AttributeBasedRowsToAxiomsQueryResultLoader(UnitOfWork uow, Class resultType, Descriptor descriptor, + EntityGraph fetchGraph, + SparqlAssemblyModifier queryAssemblyModifier) { this.uow = uow; this.resultType = resultType; this.descriptor = descriptor; + this.queryAssemblyModifier = queryAssemblyModifier; this.entityType = uow.getMetamodel().entity(resultType); + this.fetchGraph = fetchGraph; + } + + @Override + public void init(ResultSet resultSet) { + if (queryAssemblyModifier != null) { + queryAssemblyModifier.accept(this); + } else { + createProjectionMappings(resultSet.getColumnNames()); + } + } + + private void createProjectionMappings(List projectedVars) { + if (mappings != null) { + return; + } + assert !projectedVars.isEmpty(); + final String subjectVar = projectedVars.get(0); + this.mappings = new ArrayList<>(projectedVars.size() - 1); + final Map> atts = FetchGraphProcessor.attributes(entityType).stream() + .collect(Collectors.toMap(Attribute::getName, Function.identity())); + for (int i = 1; i < projectedVars.size(); i++) { + final String projectedVar = projectedVars.get(i); + if (atts.containsKey(projectedVar)) { + // ?stringAttribute + mappings.add(new QueryVariableMapping(subjectVar, projectedVar, atts.get(projectedVar))); + } else if (atts.containsKey(projectedVar.substring(subjectVar.length() + 1))) { + // ?x_stringAttribute + mappings.add(new QueryVariableMapping(subjectVar, projectedVar, atts.get(projectedVar.substring(subjectVar.length() + 1)))); + } else if (projectedVar.endsWith(AttributeEnumeratingSparqlAssemblyModifier.TYPES_VAR_NAME)) { + // ?x_types or ?types + mappings.add(new QueryVariableMapping(subjectVar, projectedVar, entityType.getTypes())); + } else { + LOG.warn("Variable '{}' projected from the query cannot be mapped to any attributes in entity class {}.", projectedVar, entityType); + } + } } @Override @@ -85,6 +142,7 @@ public Optional loadResult(ResultRow resultRow) { final URI subject = resultRow.getObject(0, URI.class); if (currentSubject == null) { reset(NamedResource.create(subject)); + createProjectionMappings(resultRow.getColumnNames()); } if (subject.equals(currentSubject.getIdentifier())) { rowToAxioms(resultRow); @@ -107,42 +165,52 @@ private void reset(NamedResource newSubject) { } private void rowToAxioms(ResultRow row) throws OntoDriverException { - final VariableNameMapper varNameMapper = createVarNameMapper(row); - for (Attribute attribute : entityType.getAttributes()) { - final String varName = varNameMapper.getAttributeVarName(attribute); - if (row.isBound(varName)) { - currentEntityAxioms.add(new AxiomImpl<>(currentSubject, attributeToAssertion(attribute), new Value<>(row.getObject(varName)))); + for (QueryVariableMapping mapping : mappings) { + if (row.isBound(mapping.attributeVar())) { + assert row.isBound(mapping.subjectVar()); + final NamedResource subject = NamedResource.create(row.getObject(mapping.subjectVar(), URI.class)); + final Assertion assertion = attributeToAssertion(mapping.attribute()); + extractValue(row, mapping).map(v -> new AxiomImpl<>(subject, assertion, v)) + .forEach(currentEntityAxioms::add); } } - final Optional> typesSpec = Optional.ofNullable(entityType.getTypes()); - final String varName = varNameMapper.getTypesVarName(typesSpec.orElse(null)); - if (row.isBound(varName)) { - currentEntityAxioms.add(new AxiomImpl<>(currentSubject, - Assertion.createClassAssertion(typesSpec.map(FieldSpecification::isInferred).orElse(false)), - new Value<>(row.getObject(varName)))); - } } - private VariableNameMapper createVarNameMapper(ResultRow row) { - assert !row.getColumnNames().isEmpty(); - final String subjectVarName = row.getColumnNames().get(0); - final boolean optimized = row.getColumnNames().stream() - .allMatch(name -> name.startsWith(subjectVarName)); - return optimized ? new OptimizedQueryVariableNameMapper(subjectVarName) : new DefaultVariableNameMapper(); + private static Assertion attributeToAssertion(FieldSpecification fs) { + if (fs != null && fs.isMappedAttribute()) { + final Attribute attribute = (Attribute) fs; + return switch (attribute.getPersistentAttributeType()) { + case OBJECT -> + Assertion.createObjectPropertyAssertion(attribute.getIRI().toURI(), attribute.isInferred()); + case DATA -> Assertion.createDataPropertyAssertion(attribute.getIRI().toURI(), attribute.isInferred()); + case ANNOTATION -> + Assertion.createAnnotationPropertyAssertion(attribute.getIRI().toURI(), attribute.isInferred()); + }; + } else { + if (fs != null) { + assert fs instanceof TypesSpecification; + return Assertion.createClassAssertion(fs.isInferred()); + } else { + return Assertion.createClassAssertion(false); + } + } } - private static Assertion attributeToAssertion(Attribute attribute) { - return switch (attribute.getPersistentAttributeType()) { - case OBJECT -> Assertion.createObjectPropertyAssertion(attribute.getIRI().toURI(), attribute.isInferred()); - case DATA -> Assertion.createDataPropertyAssertion(attribute.getIRI().toURI(), attribute.isInferred()); - case ANNOTATION -> - Assertion.createAnnotationPropertyAssertion(attribute.getIRI().toURI(), attribute.isInferred()); - }; + private Stream> extractValue(ResultRow row, + QueryVariableMapping variableMapping) throws OntoDriverException { + if (variableMapping.canGroupConcat()) { + final String values = row.getString(variableMapping.attributeVar()); + // This does not handle blank nodes + return Stream.of(values.split(AttributeEnumeratingSparqlAssemblyModifier.GROUP_CONCAT_SEPARATOR)) + .map(URI::create).map(Value::new); + } else { + return Stream.of(new Value<>(row.getObject(variableMapping.attributeVar()))); + } } private T loadEntity() { try { - return uow.readObjectFromAxioms(resultType, currentEntityAxioms, descriptor); + return uow.readObjectFromAxioms(resultType, currentEntityAxioms, new AxiomBasedLoadingConfigGroup<>(currentSubject.getIdentifier(), descriptor, fetchGraph)); } catch (CardinalityConstraintViolatedException e) { // Axioms may contain more statements than expected due to query evaluation containing inferred results. // If the entity class declares ICs on non-inferred attributes, this may lead to IC violation exception. @@ -162,32 +230,8 @@ public Optional loadLastPending() { return Optional.empty(); } - private interface VariableNameMapper { - String getAttributeVarName(Attribute attribute); - String getTypesVarName(TypesSpecification types); - } - - private record OptimizedQueryVariableNameMapper(String subjectVarName) implements VariableNameMapper { - @Override - public String getAttributeVarName(Attribute attribute) { - return subjectVarName + attribute.getName(); - } - - @Override - public String getTypesVarName(TypesSpecification types) { - return subjectVarName + AttributeEnumeratingSparqlAssemblyModifier.TYPES_VAR_SUFFIX; - } - } - - private static class DefaultVariableNameMapper implements VariableNameMapper { - @Override - public String getAttributeVarName(Attribute attribute) { - return attribute.getName(); - } - - @Override - public String getTypesVarName(TypesSpecification types) { - return types != null ? types.getName() : AttributeEnumeratingSparqlAssemblyModifier.TYPES_VAR_SUFFIX; - } + @Override + public void visit(AttributeEnumeratingSparqlAssemblyModifier modifier) { + this.mappings = modifier.getVariableMapping(); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeEnumeratingSparqlAssemblyModifier.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeEnumeratingSparqlAssemblyModifier.java index 1011b6467..0dda479f0 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeEnumeratingSparqlAssemblyModifier.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeEnumeratingSparqlAssemblyModifier.java @@ -17,13 +17,10 @@ */ package cz.cvut.kbss.jopa.query.sparql.loader; -import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraint; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.model.metamodel.AbstractIdentifiableType; -import cz.cvut.kbss.jopa.model.metamodel.Attribute; -import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; -import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; import cz.cvut.kbss.jopa.query.QueryType; import cz.cvut.kbss.jopa.query.sparql.QueryAttributes; import cz.cvut.kbss.jopa.query.sparql.TokenQueryParameter; @@ -31,14 +28,7 @@ import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; import org.antlr.v4.runtime.TokenStreamRewriter; -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; import java.util.List; -import java.util.Objects; -import java.util.Optional; /** * Optimizes entity loading by modifying the query to fetch all named attributes. @@ -67,26 +57,40 @@ */ public class AttributeEnumeratingSparqlAssemblyModifier implements SparqlAssemblyModifier { - static final String TYPES_VAR_SUFFIX = "types"; + static final String TYPES_VAR_NAME = "types"; + static final String GROUP_CONCAT_SEPARATOR = "\u001F"; + + private final MetamodelImpl metamodel; private final IdentifiableEntityType resultType; private final Descriptor descriptor; + private final EntityGraph fetchGraph; + private final boolean inferredAttsInDefault; - public AttributeEnumeratingSparqlAssemblyModifier(IdentifiableEntityType resultType, Descriptor descriptor, + private List variableMapping; + + public AttributeEnumeratingSparqlAssemblyModifier(MetamodelImpl metamodel, IdentifiableEntityType resultType, + Descriptor descriptor, EntityGraph fetchGraph, ConnectionWrapper connection) { + this.metamodel = metamodel; this.resultType = resultType; this.descriptor = descriptor; + this.fetchGraph = fetchGraph != null ? fetchGraph : generateDefaultFetchGraph(); this.inferredAttsInDefault = resolveInferenceContext(connection); - assert resultType.getProperties() == null; + assert fetchGraph != null || resultType.getProperties() == null; } - private boolean resolveInferenceContext(ConnectionWrapper connection) { + private static boolean resolveInferenceContext(ConnectionWrapper connection) { return "GraphDB".equals(connection.getRepositoryMetadata().getProductName()); } + private EntityGraph generateDefaultFetchGraph() { + return new FetchGraphProcessor(metamodel).generateDefaultFetchGraph(resultType); + } + @Override public void modify(TokenStreamSparqlQueryHolder queryHolder, TokenStreamRewriter tokenRewriter, QueryAttributes queryAttributes) { @@ -94,70 +98,49 @@ public void modify(TokenStreamSparqlQueryHolder queryHolder, TokenStreamRewriter assert queryHolder.getProjectedQueryParameters().size() == 1; final TokenQueryParameter p = queryHolder.getProjectedQueryParameters().get(0); - final List variablesToProject = addAttributeSelection(queryHolder, tokenRewriter, queryAttributes); - tokenRewriter.insertAfter(p.getSingleToken(), " " + String.join(" ", variablesToProject)); + final String subjectParamName = UnboundPredicateObjectSparqlAssemblyModifier.getBaseParamName(p); + final EntityMappingQueryModifier queryModifier = new EntityMappingQueryModifier(metamodel, resultType, descriptor, inferredAttsInDefault); + final EntityMappingQueryModifier.QueryModification mod = queryModifier.modify(fetchGraph, subjectParamName); + this.variableMapping = mod.variables(); + tokenRewriter.insertBefore(queryAttributes.lastClosingCurlyBraceToken(), mod.queryPart()); + modifyProjection(p, tokenRewriter, queryAttributes, mod); } - private List addAttributeSelection(TokenStreamSparqlQueryHolder queryHolder, - TokenStreamRewriter tokenRewriter, - QueryAttributes queryAttributes) { - final StringBuilder attributePatterns = new StringBuilder(); - final List variables = new ArrayList<>(); - final String subjectParamName = UnboundPredicateObjectSparqlAssemblyModifier.getBaseParamName(queryHolder.getProjectedQueryParameters() - .get(0)); - final String subjectVariable = "?" + subjectParamName; - attributes().forEach(att -> { - final int min = Arrays.stream(att.getConstraints()).map(ParticipationConstraint::min) - .min(Comparator.naturalOrder()).orElse(0); - final String variable = "?" + subjectParamName + att.getName(); - variables.add(variable); - if (min < 1) { - attributePatterns.append("OPTIONAL { "); + private void modifyProjection(TokenQueryParameter firstProjected, TokenStreamRewriter tokenRewriter, + QueryAttributes queryAttributes, + EntityMappingQueryModifier.QueryModification queryMod) { + final StringBuilder projection = new StringBuilder(); + final StringBuilder groupBy = new StringBuilder(" GROUP BY " + firstProjected.getIdentifierAsQueryString()); + boolean hasGroupConcat = false; + for (QueryVariableMapping varMapping : queryMod.variables()) { + final String variable = "?" + varMapping.attributeVar(); + // PERF: GROUP_CONCAT prevents combinatorial blowup of the number of rows when properties have multiple values + if (varMapping.canGroupConcat()) { + projection.append(" (GROUP_CONCAT(DISTINCT ").append(variable).append("; SEPARATOR='") + .append(GROUP_CONCAT_SEPARATOR).append("') AS ").append(variable).append("_gc)"); + varMapping.setAttributeVar(varMapping.attributeVar() + "_gc"); + hasGroupConcat = true; + } else { + projection.append(' ').append(variable); + groupBy.append(' ').append(variable); } - final Optional ctx = context(att); - ctx.ifPresent(uri -> attributePatterns.append("GRAPH <").append(uri).append("> { ")); - attributePatterns.append(subjectVariable).append(" <").append(att.getIRI()) - .append("> ").append(variable).append(" . "); - if (min < 1) { - attributePatterns.append("} "); + } + tokenRewriter.insertAfter(firstProjected.getSingleToken(), projection.toString()); + if (hasGroupConcat) { + tokenRewriter.insertAfter(queryAttributes.lastClosingCurlyBraceToken(), groupBy.toString()); + if (!queryAttributes.hasOrderBy()) { + // Add ordering to ensure stable results with rows with the same subject in sequence + tokenRewriter.insertAfter(queryAttributes.lastClosingCurlyBraceToken(), " ORDER BY " + firstProjected.getIdentifierAsQueryString()); } - ctx.ifPresent(uri -> attributePatterns.append("} ")); - }); - final String variable = "?" + subjectParamName + TYPES_VAR_SUFFIX; - variables.add(variable); - final Optional ctx = typesContext(); - ctx.ifPresent(uri -> attributePatterns.append("GRAPH <").append(uri).append("> { ")); - attributePatterns.append(subjectVariable).append(" a ").append(variable).append(" . "); - ctx.ifPresent(uri -> attributePatterns.append("} ")); - tokenRewriter.insertBefore(queryAttributes.lastClosingCurlyBraceToken(), attributePatterns.toString()); - return variables; - } - - private Collection> attributes() { - final List> atts = new ArrayList<>(resultType.getAttributes()); - resultType.getSubtypes().stream() - .flatMap(subtype -> subtype.getAttributes().stream()) - .forEach(atts::add); - return atts; + } } - private Optional context(FieldSpecification att) { - assert descriptor.getAttributeContexts(att).size() <= 1; - if (att.isInferred() && inferredAttsInDefault) { - return Optional.empty(); - } - return descriptor.getSingleAttributeContext(att).map(URI::toString); + @Override + public void accept(SparqlAssemblyModifierVisitor visitor) { + visitor.visit(this); } - private Optional typesContext() { - final Optional> optionalTs = resultType.getTypes() != null ? Optional.of(resultType.getTypes()) : resultType.getSubtypes() - .stream() - .map(AbstractIdentifiableType::getTypes) - .filter(Objects::nonNull) - .findFirst(); - return optionalTs.flatMap(ts -> { - assert descriptor.getAttributeContexts(ts).size() <= 1; - return descriptor.getSingleAttributeContext(ts); - }).map(URI::toString); + public List getVariableMapping() { + return variableMapping; } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/EntityMappingQueryModifier.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/EntityMappingQueryModifier.java new file mode 100644 index 000000000..9034e2fe9 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/EntityMappingQueryModifier.java @@ -0,0 +1,181 @@ +package cz.cvut.kbss.jopa.query.sparql.loader; + +import cz.cvut.kbss.jopa.model.AttributeNode; +import cz.cvut.kbss.jopa.model.AttributeNodeImpl; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.Subgraph; +import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraint; +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.model.metamodel.AbstractIdentifiableType; +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; +import cz.cvut.kbss.jopa.utils.IdentifierTransformer; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static cz.cvut.kbss.jopa.query.sparql.loader.AttributeEnumeratingSparqlAssemblyModifier.TYPES_VAR_NAME; + +/** + * Creates a basic graph pattern corresponding to the specified entity fetch graph. + */ +class EntityMappingQueryModifier { + + private final MetamodelImpl metamodel; + private final IdentifiableEntityType resultType; + + private final Descriptor descriptor; + + private final boolean inferredAttsInDefault; + + EntityMappingQueryModifier(MetamodelImpl metamodel, IdentifiableEntityType resultType, Descriptor descriptor, + boolean inferredAttsInDefault) { + this.metamodel = metamodel; + this.resultType = resultType; + this.descriptor = descriptor; + this.inferredAttsInDefault = inferredAttsInDefault; + } + + /** + * Creates a query modification based on the specified fetch graph. + *

+ * That is only attributes specified in the graph are included in the query. + * + * @param graph Graph describing what to fetch + * @param subjectVar Name of the attributeVar representing the subject of the graph patterns. Will be used to + * generate the projected attributeVar names + * @return Query modification + */ + QueryModification modify(EntityGraph graph, String subjectVar) { + return modifyImpl(graph.getAttributeNodes(), subjectVar); + } + + /** + * Creates a query modification based on the specified fetch subgraph. + *

+ * That is only attributes specified in the subgraph are included in the query. + * + * @param graph Graph describing what to fetch + * @param subjectVar Name of the attributeVar representing the subject of the graph patterns. Will be used to + * generate the projected attributeVar names + * @return Query modification + */ + QueryModification modify(Subgraph graph, String subjectVar) { + return modifyImpl(graph.getAttributeNodes(), subjectVar); + } + + private QueryModification modifyImpl(List> attributeNodes, String subjectVarName) { + final StringBuilder attributePatterns = new StringBuilder(); + final StringBuilder optionalAttributesPatterns = new StringBuilder(); + final List variables = new ArrayList<>(); + final List variablesToAppend = new ArrayList<>(); + final String subjectVariable = "?" + subjectVarName; + attributeNodes.forEach(attNode -> { + final Attribute att = attribute(attNode); + final int min = Arrays.stream(att.getConstraints()).map(ParticipationConstraint::min) + .min(Comparator.naturalOrder()).orElse(0); + final String variable = "?" + varName(subjectVarName, att.getName()); + variables.add(new QueryVariableMapping(subjectVarName, varName(subjectVarName, att.getName()), att)); + final StringBuilder target = min < 1 ? optionalAttributesPatterns : attributePatterns; + if (min < 1) { + target.append("OPTIONAL { "); + } + final Optional ctx = context(att); + ctx.ifPresent(uri -> target.append("GRAPH <").append(uri).append("> { ")); + target.append(subjectVariable).append(" <").append(att.getIRI()) + .append("> ").append(variable).append(" . "); + if (!attNode.getSubgraphs().isEmpty()) { + attNode.getSubgraphs().forEach((cls, graph) -> { + final IdentifiableEntityType subType = metamodel.entity(cls); + final EntityMappingQueryModifier subModifier = new EntityMappingQueryModifier(metamodel, subType, descriptor.getAttributeDescriptor(att), inferredAttsInDefault); + final QueryModification mod = subModifier.modify(graph, varName(subjectVarName, att.getName())); + target.append(mod.queryPart()); + variablesToAppend.addAll(mod.variables()); + }); + } + if (min < 1) { + target.append("} "); + } + ctx.ifPresent(uri -> target.append("} ")); + }); + addTypesPattern(subjectVarName, variables, attributePatterns, subjectVariable); + if (variablesToAppend.stream().anyMatch(QueryVariableMapping::canGroupConcat)) { + variables.forEach(QueryVariableMapping::preventGroupConcat); + } + variables.addAll(variablesToAppend); + // PERF: Put OPTIONAL patterns to the end + return new QueryModification(variables, attributePatterns + optionalAttributesPatterns.toString()); + } + + private void addTypesPattern(String subjectVarName, List variables, + StringBuilder attributePatterns, + String subjectVariable) { + final String typesVariable = "?" + varName(subjectVarName, TYPES_VAR_NAME); + variables.add(new QueryVariableMapping(subjectVarName, varName(subjectVarName, TYPES_VAR_NAME), resultType.getTypes())); + final Optional ctx = typesContext(); + if (resultType.getTypes() != null) { + ctx.ifPresent(uri -> attributePatterns.append("GRAPH <").append(uri).append("> { ")); + attributePatterns.append(subjectVariable).append(" a ").append(typesVariable).append(" . "); + ctx.ifPresent(uri -> attributePatterns.append("} ")); + } else { + ctx.ifPresent(uri -> attributePatterns.append("GRAPH <").append(uri).append("> { ")); + final String typeIri = IdentifierTransformer.stringifyIri(resultType.getIRI()); + attributePatterns.append(subjectVariable).append(" a ") + .append(typeIri).append(" . "); + attributePatterns.append("BIND (").append(typeIri).append(" AS ").append(typesVariable).append(") "); + ctx.ifPresent(uri -> attributePatterns.append("} ")); + } + } + + static String varName(String subjectVar, String attName) { + return subjectVar + "_" + attName; + } + + private Attribute attribute(AttributeNode node) { + if (node instanceof AttributeNodeImpl impl) { + return impl.getAttribute(); + } + return resultType.getAttributeIncludingSubTypes(node.getAttributeName()); + } + + private Optional context(FieldSpecification att) { + assert descriptor.getAttributeContexts(att).size() <= 1; + if (att.isInferred() && inferredAttsInDefault) { + return Optional.empty(); + } + return descriptor.getSingleAttributeContext(att).map(URI::toString); + } + + private Optional typesContext() { + final Optional> optionalTs = resultType.getTypes() != null ? Optional.of(resultType.getTypes()) : resultType.getSubtypes() + .stream() + .map(AbstractIdentifiableType::getTypes) + .filter(Objects::nonNull) + .findFirst(); + return optionalTs.flatMap(ts -> { + assert descriptor.getAttributeContexts(ts).size() <= 1; + return descriptor.getSingleAttributeContext(ts); + }).map(URI::toString); + } + + /** + * Query modification. + * + * @param variables Variables to project + * @param queryPart Basic graph patterns to add to the query + */ + record QueryModification(List variables, String queryPart) { + + public List variableNames() { + return variables.stream().map(QueryVariableMapping::attributeVar).toList(); + } + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/FetchGraphProcessor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/FetchGraphProcessor.java new file mode 100644 index 000000000..89a56e9a9 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/FetchGraphProcessor.java @@ -0,0 +1,75 @@ +package cz.cvut.kbss.jopa.query.sparql.loader; + +import cz.cvut.kbss.jopa.model.AttributeNode; +import cz.cvut.kbss.jopa.model.AttributeNodeImpl; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.annotations.FetchType; +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +class FetchGraphProcessor { + + private final MetamodelImpl metamodel; + + FetchGraphProcessor(MetamodelImpl metamodel) { + this.metamodel = metamodel; + } + + EntityGraph generateDefaultFetchGraph(IdentifiableEntityType et) { + final EntityGraph graph = new EntityGraphImpl<>(et, metamodel); + graph.addAttributeNodes(attributes(et).toArray(Attribute[]::new)); + return graph; + } + + static Collection> attributes(IdentifiableEntityType et) { + final List> atts = new ArrayList<>(et.getAttributes().stream() + .filter(att -> att.getFetchType() == FetchType.EAGER) + .toList()); + et.getSubtypes().stream() + .flatMap(subtype -> subtype.getAttributes().stream()) + .filter(att -> att.getFetchType() == FetchType.EAGER) + .forEach(atts::add); + return atts; + } + + List mapFetchGraphToProjection(EntityGraph fetchGraph, + IdentifiableEntityType et, + String rootVariable) { + return mapFetchGraphToProjection(fetchGraph.getAttributeNodes(), et, rootVariable); + } + + private List mapFetchGraphToProjection(List> attributeNodes, + IdentifiableEntityType et, + String subjectVariable) { + final List mappings = new ArrayList<>(attributeNodes.size()); + final List mappingsToAppend = new ArrayList<>(); + attributeNodes.forEach(attNode -> { + final Attribute att = attribute(attNode, et); + final String attVarName = EntityMappingQueryModifier.varName(subjectVariable, attNode.getAttributeName()); + mappings.add(new QueryVariableMapping(subjectVariable, attVarName, att)); + if (!attNode.getSubgraphs().isEmpty()) { + attNode.getSubgraphs().forEach((cls, graph) -> { + final IdentifiableEntityType subType = metamodel.entity(cls); + mappingsToAppend.addAll(mapFetchGraphToProjection(graph.getAttributeNodes(), subType, attVarName)); + }); + } + }); + mappings.add(new QueryVariableMapping(subjectVariable, + EntityMappingQueryModifier.varName(subjectVariable, AttributeEnumeratingSparqlAssemblyModifier.TYPES_VAR_NAME), et.getTypes())); + mappings.addAll(mappingsToAppend); + return mappings; + } + + private Attribute attribute(AttributeNode node, IdentifiableEntityType et) { + if (node instanceof AttributeNodeImpl impl) { + return impl.getAttribute(); + } + return et.getAttributeIncludingSubTypes(node.getAttributeName()); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/NoopSparqlAssemblyModifier.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/NoopSparqlAssemblyModifier.java new file mode 100644 index 000000000..72c63c8d8 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/NoopSparqlAssemblyModifier.java @@ -0,0 +1,13 @@ +package cz.cvut.kbss.jopa.query.sparql.loader; + +import cz.cvut.kbss.jopa.query.sparql.QueryAttributes; +import cz.cvut.kbss.jopa.query.sparql.TokenStreamSparqlQueryHolder; +import org.antlr.v4.runtime.TokenStreamRewriter; + +class NoopSparqlAssemblyModifier implements SparqlAssemblyModifier { + @Override + public void modify(TokenStreamSparqlQueryHolder queryHolder, TokenStreamRewriter tokenRewriter, + QueryAttributes queryAttributes) { + // Do nothing + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/QueryResultLoadingOptimizer.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/QueryResultLoadingOptimizer.java deleted file mode 100644 index 67684a381..000000000 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/QueryResultLoadingOptimizer.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * JOPA - * Copyright (C) 2026 Czech Technical University in Prague - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package cz.cvut.kbss.jopa.query.sparql.loader; - -import cz.cvut.kbss.jopa.model.QueryResultLoader; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.query.QueryHolder; - -/** - * Allows optimizing query results by modifying the query assembly and providing a corresponding query result loader. - * - * @param Query holder type - */ -public abstract class QueryResultLoadingOptimizer { - - protected final H queryHolder; - - protected boolean optimizationEnabled; - - protected QueryResultLoadingOptimizer(H queryHolder) {this.queryHolder = queryHolder;} - - public void enableOptimization() {this.optimizationEnabled = true;} - - public void disableOptimization() {this.optimizationEnabled = false;} - - /** - * Modifies the query assembly for optimized entity loading, if possible. - * - * @param resultClass Query result class - * @param descriptor Descriptor specified for query result loading - */ - public abstract void optimizeQueryAssembly(Class resultClass, Descriptor descriptor); - - /** - * Gets loader of query results for the specified query. - *

- * If possible, a version supporting optimized entity loading is returned (depending on whether - * {@link #optimizeQueryAssembly(Class, Descriptor)} has been called and the assembled query optimized). - * - * @param resultClass Query result class - * @param descriptor Descriptor specified for results - * @param Result type - * @return Query result loader - */ - public abstract QueryResultLoader getQueryResultLoader(Class resultClass, Descriptor descriptor); -} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/QueryVariableMapping.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/QueryVariableMapping.java new file mode 100644 index 000000000..1577f7ee1 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/QueryVariableMapping.java @@ -0,0 +1,72 @@ +package cz.cvut.kbss.jopa.query.sparql.loader; + +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; + +import java.util.Objects; + +public final class QueryVariableMapping { + private final String subjectVar; + private String attributeVar; + private final FieldSpecification attribute; + + private boolean canGroupConcat; + + public QueryVariableMapping(String subjectVar, String attributeVar, FieldSpecification attribute) { + this.subjectVar = subjectVar; + this.attributeVar = attributeVar; + this.attribute = attribute; + this.canGroupConcat = resolveCanGroupConcat(); + } + + private boolean resolveCanGroupConcat() { + return isPluralAssociation() || attribute instanceof TypesSpecification; + } + + public boolean isPluralAssociation() { + return attribute != null && attribute.isMappedAttribute() && attribute.isCollection() && ((Attribute) attribute).isAssociation(); + } + + public boolean isTypes() { + return attribute == null || attribute instanceof TypesSpecification; + } + + public String subjectVar() {return subjectVar;} + + public String attributeVar() {return attributeVar;} + + public void setAttributeVar(String attributeVar) {this.attributeVar = attributeVar;} + + public FieldSpecification attribute() {return attribute;} + + public boolean canGroupConcat() {return canGroupConcat;} + + public void preventGroupConcat() { + canGroupConcat = false; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) {return true;} + if (obj == null || obj.getClass() != this.getClass()) {return false;} + var that = (QueryVariableMapping) obj; + return Objects.equals(this.subjectVar, that.subjectVar) && + Objects.equals(this.attributeVar, that.attributeVar) && + Objects.equals(this.attribute, that.attribute); + } + + @Override + public int hashCode() { + return Objects.hash(subjectVar, attributeVar, attribute); + } + + @Override + public String toString() { + return "QueryVariableMapping[" + + "subjectVar=" + subjectVar + ", " + + "attributeVar=" + attributeVar + ", " + + "attribute=" + attribute + ']'; + } + +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlAssemblyModifier.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlAssemblyModifier.java index dd2d94e71..bd54ca2a3 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlAssemblyModifier.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlAssemblyModifier.java @@ -35,4 +35,8 @@ public interface SparqlAssemblyModifier { */ void modify(TokenStreamSparqlQueryHolder queryHolder, TokenStreamRewriter tokenRewriter, QueryAttributes queryAttributes); + + default void accept(SparqlAssemblyModifierVisitor visitor) { + // Do nothing by default + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlAssemblyModifierVisitor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlAssemblyModifierVisitor.java new file mode 100644 index 000000000..85e0cc2b8 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlAssemblyModifierVisitor.java @@ -0,0 +1,12 @@ +package cz.cvut.kbss.jopa.query.sparql.loader; + +public interface SparqlAssemblyModifierVisitor { + + default void visit(AttributeEnumeratingSparqlAssemblyModifier modifier) { + // Do nothing by default + } + + default void visit(UnboundPredicateObjectSparqlAssemblyModifier modifier) { + // Do nothing by default + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizer.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizer.java index 47d1e034c..e7ec81071 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizer.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizer.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.query.sparql.loader; import cz.cvut.kbss.jopa.model.BaseEntityQueryResultLoader; +import cz.cvut.kbss.jopa.model.EntityGraph; import cz.cvut.kbss.jopa.model.NonEntityQueryResultLoader; import cz.cvut.kbss.jopa.model.QueryResultLoader; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; @@ -30,45 +31,64 @@ import org.slf4j.LoggerFactory; /** - * Allows optimizing queries that load entity instances. + * Allows optimizing query result loading by modifying the query assembly and providing a corresponding query result + * loader. */ -public class SparqlQueryResultLoadingOptimizer extends QueryResultLoadingOptimizer { +public class SparqlQueryResultLoadingOptimizer { private static final Logger LOG = LoggerFactory.getLogger(SparqlQueryResultLoadingOptimizer.class); + private final TokenStreamSparqlQueryHolder queryHolder; + private final UnitOfWork uow; private final ConnectionWrapper connection; + private boolean optimizationEnabled; + + private SparqlAssemblyModifier assemblyModifier; + public SparqlQueryResultLoadingOptimizer(TokenStreamSparqlQueryHolder queryHolder, UnitOfWork uow, ConnectionWrapper connection) { - super(queryHolder); + this.queryHolder = queryHolder; this.uow = uow; this.connection = connection; } - @Override - public void optimizeQueryAssembly(Class resultClass, Descriptor descriptor) { - switch (resolveOptimizerType(resultClass, descriptor)) { - case TRIPLE_BASED: + public void enableOptimization() {this.optimizationEnabled = true;} + + public void disableOptimization() {this.optimizationEnabled = false;} + + /** + * Modifies the query assembly for optimized entity loading, if possible. + * + * @param resultClass Query result class + * @param descriptor Descriptor specified for query result loading + * @param fetchGraph Optional fetch graph specifying which properties should be loaded + */ + public void modifyQueryAssembly(Class resultClass, Descriptor descriptor, EntityGraph fetchGraph) { + this.assemblyModifier = switch (resolveOptimizerType(resultClass, descriptor, fetchGraph)) { + case TRIPLE_BASED -> { LOG.trace("Processing query results with triple-based optimized entity loading."); - queryHolder.setAssemblyModifier(new UnboundPredicateObjectSparqlAssemblyModifier()); - break; - case ATTRIBUTE_BASED: - LOG.trace("Processing query results with attribute enumeration-based optimized attribute loading."); - queryHolder.setAssemblyModifier(new AttributeEnumeratingSparqlAssemblyModifier(uow.getMetamodel() - .entity(resultClass), descriptor, connection)); - break; - default: - // Do nothing - break; - } + yield new UnboundPredicateObjectSparqlAssemblyModifier(); + } + case ATTRIBUTE_BASED, FETCH_GRAPH_BASED -> { + LOG.trace("Processing query results with attribute enumerating optimized entity loading."); + yield new AttributeEnumeratingSparqlAssemblyModifier(uow.getMetamodel(), uow.getMetamodel() + .entity(resultClass), descriptor, fetchGraph, connection); + } + default -> new NoopSparqlAssemblyModifier(); + }; + queryHolder.setAssemblyModifier(assemblyModifier); } - private OptimizerType resolveOptimizerType(Class resultClass, Descriptor descriptor) { - if (!canOptimize(resultClass, descriptor)) { + private OptimizerType resolveOptimizerType(Class resultClass, Descriptor descriptor, EntityGraph fetchGraph) { + if (!canOptimize(resultClass, descriptor, fetchGraph)) { return OptimizerType.NONE; } + if (fetchGraph != null) { + return OptimizerType.FETCH_GRAPH_BASED; + } final IdentifiableEntityType et = uow.getMetamodel().entity(resultClass); if (et.getProperties() == null && !et.hasSubtypes()) { return OptimizerType.ATTRIBUTE_BASED; @@ -79,8 +99,8 @@ private OptimizerType resolveOptimizerType(Class resultClass, Descriptor desc return OptimizerType.NONE; } - private boolean canOptimize(Class resultClass, Descriptor descriptor) { - return optimizationEnabled && queryHolder.getQueryType() == QueryType.SELECT + private boolean canOptimize(Class resultClass, Descriptor descriptor, EntityGraph fetchGraph) { + return (optimizationEnabled || fetchGraph != null) && queryHolder.getQueryType() == QueryType.SELECT && projectsEntity(resultClass) && limitOrOffsetNotSet() && descriptorSpecifiesAtMostOneContext(descriptor); } @@ -116,22 +136,25 @@ private boolean queryContainsGraphOrServiceClause() { * * @param resultClass Result class * @param descriptor Descriptor specified for results + * @param fetchGraph Entity graph specifying which properties should be loaded * @param Result type * @return Query result loader */ - @Override - public QueryResultLoader getQueryResultLoader(Class resultClass, Descriptor descriptor) { - final OptimizerType optimizerType = resolveOptimizerTypeForResultLoader(resultClass, descriptor); + public QueryResultLoader getQueryResultLoader(Class resultClass, Descriptor descriptor, + EntityGraph fetchGraph) { + final OptimizerType optimizerType = resolveOptimizerTypeForResultLoader(resultClass, descriptor, fetchGraph); return switch (optimizerType) { case TRIPLE_BASED -> new TripleBasedRowsToAxiomsQueryResultLoader<>(uow, resultClass, descriptor); - case ATTRIBUTE_BASED -> new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, resultClass, descriptor); + case ATTRIBUTE_BASED, FETCH_GRAPH_BASED -> + new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, resultClass, descriptor, fetchGraph, assemblyModifier); default -> uow.isEntityType(resultClass) ? new BaseEntityQueryResultLoader<>(uow, resultClass, descriptor) : new NonEntityQueryResultLoader<>(resultClass); }; } - private OptimizerType resolveOptimizerTypeForResultLoader(Class resultClass, Descriptor descriptor) { - final OptimizerType ot = resolveOptimizerType(resultClass, descriptor); + private OptimizerType resolveOptimizerTypeForResultLoader(Class resultClass, Descriptor descriptor, + EntityGraph fetchGraph) { + final OptimizerType ot = resolveOptimizerType(resultClass, descriptor, fetchGraph); if (ot != OptimizerType.NONE) { return ot; } @@ -145,6 +168,7 @@ private OptimizerType resolveOptimizerTypeForResultLoader(Class resultCla private enum OptimizerType { TRIPLE_BASED, ATTRIBUTE_BASED, + FETCH_GRAPH_BASED, NONE } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/TripleBasedRowsToAxiomsQueryResultLoader.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/TripleBasedRowsToAxiomsQueryResultLoader.java index 4f6377984..fe4319460 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/TripleBasedRowsToAxiomsQueryResultLoader.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/TripleBasedRowsToAxiomsQueryResultLoader.java @@ -24,6 +24,7 @@ import cz.cvut.kbss.jopa.model.metamodel.Attribute; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; import cz.cvut.kbss.jopa.vocabulary.RDF; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.iteration.ResultRow; @@ -43,7 +44,7 @@ /** * Loads entity by aggregating rows with the same subject into axioms and then using - * {@link UnitOfWork#readObjectFromAxioms(Class, Collection, Descriptor)} to read the entity. + * {@link UnitOfWork#readObjectFromAxioms(Class, Collection, AxiomBasedLoadingConfigGroup)} to read the entity. *

* It expects the query result rows to have three columns corresponding to the triple subject, property and object. *

@@ -112,7 +113,7 @@ private Assertion propertyToAssertion(URI property) { private T loadEntity() { try { - return uow.readObjectFromAxioms(resultType, currentEntityAxioms, descriptor); + return uow.readObjectFromAxioms(resultType, currentEntityAxioms, new AxiomBasedLoadingConfigGroup<>(currentSubject.getIdentifier(), descriptor)); } catch (CardinalityConstraintViolatedException e) { // Axioms may contain more statements than expected due to query evaluation containing inferred results. // If the entity class declares ICs on non-inferred attributes, this may lead to IC violation exception. diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/UnboundPredicateObjectSparqlAssemblyModifier.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/UnboundPredicateObjectSparqlAssemblyModifier.java index e9c68f467..52560c279 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/UnboundPredicateObjectSparqlAssemblyModifier.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/UnboundPredicateObjectSparqlAssemblyModifier.java @@ -88,4 +88,9 @@ private static boolean requiresDot(TokenStream tokenStream, Token lastClosingCur } return true; } + + @Override + public void accept(SparqlAssemblyModifierVisitor visitor) { + visitor.visit(this); + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java index 949e70268..9931f1fe0 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java @@ -20,8 +20,8 @@ import cz.cvut.kbss.jopa.exceptions.EntityNotFoundException; import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; -import cz.cvut.kbss.jopa.proxy.IndirectWrapper; -import cz.cvut.kbss.jopa.sessions.cache.CacheManager; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; import cz.cvut.kbss.jopa.model.EntityState; import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; import cz.cvut.kbss.jopa.model.LoadState; @@ -33,8 +33,10 @@ import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; +import cz.cvut.kbss.jopa.proxy.IndirectWrapper; import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; import cz.cvut.kbss.jopa.sessions.cache.Descriptors; import cz.cvut.kbss.jopa.sessions.change.Change; import cz.cvut.kbss.jopa.sessions.change.ChangeCalculator; @@ -44,6 +46,7 @@ import cz.cvut.kbss.jopa.sessions.change.UnitOfWorkChangeSet; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingParameters; import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; @@ -356,16 +359,17 @@ public T readObjectWithoutRegistration(Class cls, Object identifier, Desc } @Override - public T readObjectFromAxioms(Class cls, Collection> axioms, Descriptor descriptor) { + public T readObjectFromAxioms(Class cls, Collection> axioms, + AxiomBasedLoadingConfigGroup config) { Objects.requireNonNull(cls); Objects.requireNonNull(axioms); - Objects.requireNonNull(descriptor); + Objects.requireNonNull(config); - final T result = storage.loadFromAxioms(new AxiomBasedLoadingParameters<>(cls, descriptor, false, axioms)); + final T result = storage.loadFromAxioms(new AxiomBasedLoadingParameters<>(cls, axioms, config)); if (result == null) { return null; } - final Object clone = registerExistingObject(result, new CloneRegistrationDescriptor(descriptor).postCloneHandlers(List.of(new PostLoadInvoker(getMetamodel())))); + final Object clone = registerExistingObject(result, new CloneRegistrationDescriptor(config.descriptor()).postCloneHandlers(List.of(new PostLoadInvoker(getMetamodel())))); return cls.cast(clone); } @@ -486,8 +490,8 @@ protected IdentifiableEntityType entityType(Class cls) { } /** - * This method calculates the changes to the registered entities and adds these changes into the given - * change set for future commit to the ontology. + * This method calculates the changes to the registered entities and adds these changes into the given change set + * for future commit to the ontology. */ void calculateChanges() { if (hasNew) { @@ -951,7 +955,15 @@ public boolean isInferred(T entity, FieldSpecification attribu public LoadState isLoaded(Object entity, String attributeName) { Objects.requireNonNull(entity); final FieldSpecification fs = entityType(entity.getClass()).getFieldSpecification(attributeName); - return loadStateRegistry.contains(entity) ? loadStateRegistry.get(entity).isLoaded(fs) : LoadState.UNKNOWN; + return isLoaded(entity, fs); + } + + @Override + public LoadState isLoaded(Object entity, FieldSpecification fieldSpec) { + Objects.requireNonNull(entity); + Objects.requireNonNull(fieldSpec); + return loadStateRegistry.contains(entity) ? loadStateRegistry.get(entity) + .isLoaded(fieldSpec) : LoadState.UNKNOWN; } @Override @@ -960,14 +972,21 @@ public LoadState isLoaded(Object entity) { return loadStateRegistry.contains(entity) ? loadStateRegistry.get(entity).isLoaded() : LoadState.UNKNOWN; } + @Override public SparqlQueryFactory sparqlQueryFactory() { return queryFactory; } + @Override public CriteriaBuilder getCriteriaBuilder() { return parent.getCriteriaBuilder(); } + @Override + public EntityGraph createEntityGraph(Class rootType) { + return new EntityGraphImpl<>(getMetamodel().entity(rootType), getMetamodel()); + } + void registerEntityWithOntologyContext(Object entity, Descriptor descriptor) { assert descriptor != null; assert entity != null; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ReadOnlyUnitOfWork.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ReadOnlyUnitOfWork.java index 96f207f62..817fae2f1 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ReadOnlyUnitOfWork.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ReadOnlyUnitOfWork.java @@ -28,6 +28,7 @@ import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingParameters; import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; @@ -140,17 +141,18 @@ public T getManagedOriginal(Class cls, Object identifier, Descriptor desc } @Override - public T readObjectFromAxioms(Class cls, Collection> axioms, Descriptor descriptor) { + public T readObjectFromAxioms(Class cls, Collection> axioms, + AxiomBasedLoadingConfigGroup config) { Objects.requireNonNull(cls); Objects.requireNonNull(axioms); - Objects.requireNonNull(descriptor); + Objects.requireNonNull(config); - final AxiomBasedLoadingParameters loadingParameters = new AxiomBasedLoadingParameters<>(cls, descriptor, true, axioms); + final AxiomBasedLoadingParameters loadingParameters = new AxiomBasedLoadingParameters<>(cls, axioms, config); final T result = storage.loadFromAxioms(loadingParameters); if (result == null) { return null; } - return cls.cast(registerExistingObject(result, descriptor)); + return cls.cast(registerExistingObject(result, config.descriptor())); } /** diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java index 37a96bc50..d01d9f43b 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java @@ -18,12 +18,14 @@ package cz.cvut.kbss.jopa.sessions; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.model.EntityGraph; import cz.cvut.kbss.jopa.model.EntityState; import cz.cvut.kbss.jopa.model.LoadState; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.utils.Wrapper; @@ -173,14 +175,17 @@ public interface UnitOfWork extends ConfigurationHolder, MetamodelProvider, Wrap * Reads an object from the specified axioms. *

* It is assumed the axioms correspond to the provided descriptor. + *

+ * The axioms may represent an entity graph, i.e. multiple interconnected entities. It is the responsibility of the + * loader to ensure the entity graph is reconstructed correctly w.r.t. the provided fetch graph. * - * @param cls Expected result class - * @param axioms Axioms representing the object - * @param descriptor Entity descriptor - * @param Return type + * @param cls Expected result class + * @param axioms Axioms representing the object(s) + * @param config Additional loading configuration + * @param Return type * @return The retrieved object or {@code null} if no object of the expected target class can be reconstructed */ - T readObjectFromAxioms(Class cls, Collection> axioms, Descriptor descriptor); + T readObjectFromAxioms(Class cls, Collection> axioms, AxiomBasedLoadingConfigGroup config); /** * Retrieves a reference to an object with the specified identifier. @@ -322,12 +327,22 @@ public interface UnitOfWork extends ConfigurationHolder, MetamodelProvider, Wrap * Gets the load status of the specified attribute on the specified entity. * * @param entity Entity instance - * @param attributeName Attribute whose load status is to be determined + * @param attributeName Name of the attribute whose load status is to be determined * @return Attribute load status * @see cz.cvut.kbss.jopa.model.ProviderUtil#isLoadedWithoutReference(Object, String) */ LoadState isLoaded(Object entity, String attributeName); + /** + * Gets the load status of the specified attribute on the specified entity. + * + * @param entity Entity instance + * @param fieldSpec Attribute whose load status is to be determined + * @return Attribute load status + * @see cz.cvut.kbss.jopa.model.ProviderUtil#isLoadedWithoutReference(Object, String) + */ + LoadState isLoaded(Object entity, FieldSpecification fieldSpec); + /** * Gets the load status of the specified entity. * @@ -418,4 +433,13 @@ public interface UnitOfWork extends ConfigurationHolder, MetamodelProvider, Wrap * @return Criteria query builder */ CriteriaBuilder getCriteriaBuilder(); + + /** + * Creates an {@link EntityGraph} instance that can be used to specify an entity graph. + * + * @param rootType Root entity class + * @param Root entity class type + * @return Entity graph + */ + EntityGraph createEntityGraph(Class rootType); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactory.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactory.java index 06885a74e..91223158e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactory.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactory.java @@ -17,11 +17,15 @@ */ package cz.cvut.kbss.jopa.sessions.descriptor; +import cz.cvut.kbss.jopa.model.AttributeNodeImpl; import cz.cvut.kbss.jopa.model.LoadState; import cz.cvut.kbss.jopa.model.annotations.FetchType; +import cz.cvut.kbss.jopa.model.metamodel.Attribute; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; /** @@ -69,6 +73,34 @@ public static LoadStateDescriptor createAllUnknown(T instance, EntityType return new LoadStateDescriptor<>(instance, et, LoadState.UNKNOWN); } + /** + * Creates an instance descriptor for specified instance based on the specified fetch graph. + *

+ * The descriptor will look as follows: + *

    + *
  • Attributes specified in the fetch graph - {@link LoadState#LOADED}
  • + *
  • Object-property attributes - {@link LoadState#NOT_LOADED}
  • + *
  • Other attributes - {@link LoadState#UNKNOWN}
  • + *
+ * + * @param instance Instance to create descriptor for + * @param et Entity type of the instance + * @param fetchGraph Entity fetch graph + * @param Instance type + * @return Fresh instance descriptor + */ + public static LoadStateDescriptor createForFetchGraph(T instance, IdentifiableEntityType et, + FetchGraphWrapper fetchGraph) { + final LoadStateDescriptor descriptor = createAllUnknown(instance, et); + et.getAttributes().stream().filter(Attribute::isAssociation) + .forEach(att -> descriptor.setLoaded(att, LoadState.NOT_LOADED)); + fetchGraph.getAttributeNodes().forEach(an -> { + final Attribute fs = (an instanceof AttributeNodeImpl attNode) ? attNode.getAttribute() : et.getAttributeIncludingSubTypes(an.getAttributeName()); + descriptor.setLoaded(fs, LoadState.LOADED); + }); + return descriptor; + } + /** * Creates an instance descriptor which sets load status of attributes based on their value in the specified * instance as follows: diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/AxiomBasedLoadingConfigGroup.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/AxiomBasedLoadingConfigGroup.java new file mode 100644 index 000000000..d8c3dc83d --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/AxiomBasedLoadingConfigGroup.java @@ -0,0 +1,20 @@ +package cz.cvut.kbss.jopa.sessions.util; + +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; + +import java.net.URI; + +/** + * Helper configuration for loading entities directly from axioms. + * + * @param subject (Root) entity identifier + * @param descriptor Entity data descriptor + * @param fetchGraph Fetch graph, possibly {@code null} + */ +public record AxiomBasedLoadingConfigGroup(URI subject, Descriptor descriptor, EntityGraph fetchGraph) { + + public AxiomBasedLoadingConfigGroup(URI subject, Descriptor descriptor) { + this(subject, descriptor, null); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/AxiomBasedLoadingParameters.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/AxiomBasedLoadingParameters.java index 66135ad82..831ec9afa 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/AxiomBasedLoadingParameters.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/AxiomBasedLoadingParameters.java @@ -17,7 +17,6 @@ */ package cz.cvut.kbss.jopa.sessions.util; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.ontodriver.model.Axiom; import java.util.Collection; @@ -25,12 +24,21 @@ /** * Loading parameters for loading an entity from axioms. * - * @param cls Entity class - * @param descriptor Entity descriptor - * @param bypassCache Whether to bypass cache - * @param axioms Axioms to load the entity from - * @param Entity type + * @param cls Entity class + * @param axioms Axioms to load the entity from + * @param config Additional config for loading entity based on axioms + * @param Entity type */ -public record AxiomBasedLoadingParameters(Class cls, Descriptor descriptor, boolean bypassCache, - Collection> axioms) { +public record AxiomBasedLoadingParameters(Class cls, Collection> axioms, + AxiomBasedLoadingConfigGroup config) { + + /** + * Returns {@code true} to indicate that the cache should always be bypassed when loading entity directly from + * provided axioms. + * + * @return {@code true} + */ + public boolean bypassCache() { + return true; + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/FetchGraphWrapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/FetchGraphWrapper.java new file mode 100644 index 000000000..f52b77678 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/FetchGraphWrapper.java @@ -0,0 +1,79 @@ +package cz.cvut.kbss.jopa.sessions.util; + +import cz.cvut.kbss.jopa.model.AttributeNode; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.Subgraph; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; + +import java.util.List; +import java.util.Objects; + +/** + * Wrapper for {@link EntityGraph} or {@link Subgraph} providing a unified API. + */ +public class FetchGraphWrapper { + + private final List> attributeNodes; + + public FetchGraphWrapper() { + this.attributeNodes = null; + } + + private FetchGraphWrapper(List> attributeNodes) { + this.attributeNodes = attributeNodes; + } + + public FetchGraphWrapper(EntityGraph entityGraph) { + this.attributeNodes = entityGraph != null ? entityGraph.getAttributeNodes() : null; + } + + public FetchGraphWrapper(Subgraph subgraph) { + assert subgraph != null; + this.attributeNodes = subgraph.getAttributeNodes(); + } + + public List> getAttributeNodes() { + return attributeNodes; + } + + public boolean isPresent() { + return attributeNodes != null; + } + + public boolean hasAttribute(FieldSpecification att) { + return isPresent() && attributeNodes.stream().anyMatch(an -> att.getName().equals(an.getAttributeName())); + } + + /** + * Gets a subgraph for the specified attribute (if present). + *

+ * Empty {@link FetchGraphWrapper} is returned if the attribute is not present or this wrapper is empty. + * + * @param att Attribute to get subgraph for + * @return Fetch graph wrapper + */ + public FetchGraphWrapper getAttributeSubgraph(FieldSpecification att) { + if (!isPresent()) { + return new FetchGraphWrapper(); + } + return attributeNodes.stream() + .filter(an -> att.getName().equals(an.getAttributeName())) + .findFirst().map(an -> new FetchGraphWrapper(an.getSubgraphs().values().stream() + .flatMap(sg -> sg.getAttributeNodes() + .stream()).toList())) + .orElse(new FetchGraphWrapper()); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FetchGraphWrapper that)) { + return false; + } + return Objects.equals(attributeNodes, that.attributeNodes); + } + + @Override + public int hashCode() { + return Objects.hashCode(attributeNodes); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/LoadingParameters.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/LoadingParameters.java index a77615ebe..60a065bcf 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/LoadingParameters.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/LoadingParameters.java @@ -21,17 +21,23 @@ import java.net.URI; -public record LoadingParameters (Class entityClass, URI identifier, Descriptor descriptor, boolean forceEager, boolean bypassCache) { +public record LoadingParameters(Class entityClass, URI identifier, Descriptor descriptor, + FetchGraphWrapper fetchGraph, boolean forceEager, boolean bypassCache) { public LoadingParameters(Class cls, URI identifier, Descriptor descriptor) { - this(cls, identifier, descriptor, false, false); + this(cls, identifier, descriptor, new FetchGraphWrapper(), false, false); } public LoadingParameters(Class cls, URI identifier, Descriptor descriptor, boolean forceEager) { - this(cls, identifier, descriptor, forceEager, false); + this(cls, identifier, descriptor, new FetchGraphWrapper(), forceEager, false); + } + + public LoadingParameters(Class cls, URI identifier, Descriptor descriptor, boolean forceEager, + boolean bypassCache) { + this(cls, identifier, descriptor, new FetchGraphWrapper(), forceEager, bypassCache); } public LoadingParameters withBypassCache() { - return new LoadingParameters<>(entityClass, identifier, descriptor, forceEager, true); + return new LoadingParameters<>(entityClass, identifier, descriptor, fetchGraph, forceEager, true); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassL.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassL.java index b5f71f453..e3b99b36e 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassL.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassL.java @@ -34,20 +34,20 @@ public class OWLClassL implements HasUri { @Sequence(type = SequenceType.simple) @OWLObjectProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#C-hasSimpleSequence") @ParticipationConstraints({ - @ParticipationConstraint(min = 1, owlObjectIRI = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassA") + @ParticipationConstraint(min = 1, owlObjectIRI = Vocabulary.c_OwlClassA) }) private List simpleList; @Sequence(type = SequenceType.referenced) @OWLObjectProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#C-hasReferencedSequence") @ParticipationConstraints({ - @ParticipationConstraint(max = 2, owlObjectIRI = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassA") + @ParticipationConstraint(max = 2, owlObjectIRI = Vocabulary.c_OwlClassA) }) private List referencedList; - @OWLObjectProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#hasA") + @OWLObjectProperty(iri = Vocabulary.p_h_hasA) @ParticipationConstraints({ - @ParticipationConstraint(min = 1, max = 5, owlObjectIRI = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassA") + @ParticipationConstraint(min = 1, max = 5, owlObjectIRI = Vocabulary.c_OwlClassA) }) private Set set; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Person.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Person.java index ae9d40fa9..de849ec05 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Person.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Person.java @@ -50,6 +50,13 @@ public class Person implements HasUri { @Types private Set types; + public Person() { + } + + public Person(URI uri) { + this.uri = uri; + } + public URI getUri() { return uri; } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Phone.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Phone.java index 7125c9d68..28e454c03 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Phone.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Phone.java @@ -20,6 +20,7 @@ import cz.cvut.kbss.jopa.model.annotations.Id; import cz.cvut.kbss.jopa.model.annotations.OWLClass; import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; +import cz.cvut.kbss.jopa.utils.IdentifierTransformer; import java.net.URI; @@ -34,4 +35,46 @@ public class Phone { @OWLDataProperty(iri = Vocabulary.p_p_phoneBrand, simpleLiteral = true) private String brand; + + public Phone() { + } + + public Phone(URI uri, String number, String brand) { + this.uri = uri; + this.number = number; + this.brand = brand; + } + + public URI getUri() { + return uri; + } + + public void setUri(URI uri) { + this.uri = uri; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + @Override + public String toString() { + return "Phone{" + + IdentifierTransformer.stringifyIri(uri) + + ", number='" + number + '\'' + + ", brand='" + brand + '\'' + + '}'; + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/Generators.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/Generators.java index c98b208c2..aeece9623 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/Generators.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/Generators.java @@ -163,9 +163,9 @@ public static List generateInstances(int size) { public static Set generateTypes(int count) { final Set types = new HashSet<>(count); - final int randomBase = RAND.nextInt(10000); + final int base = RAND.nextInt(10000); for (int i = 0; i < count; i++) { - types.add(Vocabulary.CLASS_BASE + (randomBase + i * 2)); + types.add(Vocabulary.CLASS_BASE + base + i); } return types; } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/MetamodelFactory.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/MetamodelFactory.java index ff8ebc437..c612e9944 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/MetamodelFactory.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/MetamodelFactory.java @@ -170,7 +170,8 @@ public static void initOWLClassAMocks(IdentifiableEntityType etMock, initIdentifier(etMock, idMock, OWLClassA.class.getDeclaredField("uri"), false); } - private static void initEntityType(IdentifiableEntityType et, Class cls, EntityLifecycleListenerManager listenerManager) { + private static void initEntityType(IdentifiableEntityType et, Class cls, + EntityLifecycleListenerManager listenerManager) { when(et.getJavaType()).thenReturn(cls); when(et.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(cls)); when(et.getPersistenceType()).thenReturn(Type.PersistenceType.ENTITY); @@ -347,7 +348,7 @@ public static void initOWLClassCMocks(IdentifiableEntityType etMock, when(rdfCollectionMock.getName()).thenReturn(OWLClassC.getRdfCollectionField().getName()); when(etMock.getFieldSpecification(rdfCollectionMock.getName())).thenReturn(rdfCollectionMock); when(rdfCollectionMock.getIRI()).thenReturn(IRI.create(OWLClassC.getRdfCollectionField() - .getAnnotation(OWLObjectProperty.class).iri())); + .getAnnotation(OWLObjectProperty.class).iri())); when(rdfCollectionMock.getBindableJavaType()).thenReturn(OWLClassA.class); when(rdfCollectionMock.getElementType()).thenReturn(etAMock); when(rdfCollectionMock.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.OBJECT); @@ -460,7 +461,7 @@ public static void initOWLClassHMocks(IdentifiableEntityType etMock, SingularAttributeImpl clsGMock, IdentifiableEntityType etAMock, IdentifiableEntityType etGMock, Identifier idMock) throws NoSuchFieldException, SecurityException { - initEntityType(etMock, OWLClassH.class, EntityLifecycleListenerManager.empty()); + initEntityType(etMock, OWLClassH.class, EntityLifecycleListenerManager.empty()); when(etMock.getAttributes()).thenReturn(Set.of(clsAMock, clsGMock)); when(etMock.getFieldSpecifications()).thenReturn(Set.of(clsAMock, clsGMock, idMock)); @@ -474,7 +475,7 @@ public static void initOWLClassHMocks(IdentifiableEntityType etMock, public static void initOWLClassIMocks(IdentifiableEntityType etMock, SingularAttributeImpl aAttMock, IdentifiableEntityType etAMock, Identifier idMock) throws NoSuchFieldException, SecurityException { - initEntityType(etMock, OWLClassI.class, EntityLifecycleListenerManager.empty()); + initEntityType(etMock, OWLClassI.class, EntityLifecycleListenerManager.empty()); when(etMock.getAttributes()).thenReturn(Collections.singleton(aAttMock)); when(etMock.getFieldSpecifications()).thenReturn(Set.of(aAttMock, idMock)); @@ -486,33 +487,33 @@ public static void initOWLClassIMocks(IdentifiableEntityType etMock, public static void initOWLClassJMocks(IdentifiableEntityType etMock, AbstractPluralAttribute setAMock, IdentifiableEntityType etAMock, Identifier idMock) throws NoSuchFieldException, SecurityException { - initEntityType(etMock, OWLClassJ.class,EntityLifecycleListenerManager.empty()); + initEntityType(etMock, OWLClassJ.class, EntityLifecycleListenerManager.empty()); when(etMock.getAttributes()).thenReturn(Collections.singleton(setAMock)); when(etMock.getFieldSpecifications()).thenReturn(Set.of(setAMock, idMock)); initAttribute(etMock, setAMock, new AttributeInfo(OWLClassJ.getOwlClassAField(), Attribute.PersistentAttributeType.OBJECT).collectionType(CollectionType.SET) .elementType(OWLClassA.class).valueType(etAMock) - .constraints(new ParticipationConstraint() { - @Override - public Class annotationType() { - return ParticipationConstraint.class; - } - - @Override - public String owlObjectIRI() { - return ""; - } - - @Override - public int min() { - return 1; - } - - @Override - public int max() { - return -1; - } - })); + .constraints(new ParticipationConstraint() { + @Override + public Class annotationType() { + return ParticipationConstraint.class; + } + + @Override + public String owlObjectIRI() { + return ""; + } + + @Override + public int min() { + return 1; + } + + @Override + public int max() { + return -1; + } + })); initIdentifier(etMock, idMock, OWLClassJ.class.getDeclaredField("uri"), false); } @@ -925,8 +926,8 @@ public static void initPhoneMocks(IdentifiableEntityType etMock, Singular SingularAttributeImpl phoneBrandAttMock, Identifier idMock) throws NoSuchFieldException, SecurityException { initEntityType(etMock, Phone.class, EntityLifecycleListenerManager.empty()); - when(etMock.getAttributes()).thenReturn(Collections.singleton(phoneNumberAttMock)); - when(etMock.getFieldSpecifications()).thenReturn(Set.of(phoneNumberAttMock, idMock)); + when(etMock.getAttributes()).thenReturn(Set.of(phoneNumberAttMock, phoneBrandAttMock)); + when(etMock.getFieldSpecifications()).thenReturn(Set.of(phoneNumberAttMock, phoneBrandAttMock, idMock)); initAttribute(etMock, phoneNumberAttMock, new AttributeInfo(Phone.class.getDeclaredField("number"), Attribute.PersistentAttributeType.DATA)); initAttribute(etMock, phoneBrandAttMock, new AttributeInfo(Phone.class.getDeclaredField("brand"), Attribute.PersistentAttributeType.DATA)); initIdentifier(etMock, idMock, Phone.class.getDeclaredField("uri"), false); @@ -943,8 +944,8 @@ public static void initPersonMocks(IdentifiableEntityType etMock, Identifier idMock) throws NoSuchFieldException, SecurityException { initEntityType(etMock, Person.class, EntityLifecycleListenerManager.empty()); initIdentifier(etMock, idMock, Person.class.getDeclaredField("uri"), false); - when(etMock.getAttributes()).thenReturn(Set.of(usernameAttMock, genderAttMock, ageAttMock, phoneAttMock)); - when(etMock.getFieldSpecifications()).thenReturn(Set.of(usernameAttMock, genderAttMock, ageAttMock, phoneAttMock, idMock)); + when(etMock.getAttributes()).thenReturn(Set.of(firstNameAttMock, lastNameAttMock, usernameAttMock, genderAttMock, ageAttMock, phoneAttMock)); + when(etMock.getFieldSpecifications()).thenReturn(Set.of(firstNameAttMock, lastNameAttMock, usernameAttMock, genderAttMock, ageAttMock, phoneAttMock, idMock)); initAttribute(etMock, firstNameAttMock, new AttributeInfo(Person.class.getDeclaredField("firstName"), Attribute.PersistentAttributeType.DATA).language(null)); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/QueryTestBase.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/QueryTestBase.java index 617e077dd..e3b86acd8 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/QueryTestBase.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/QueryTestBase.java @@ -303,13 +303,13 @@ void setParameterSupportsUsingEntityAsParameterValue() throws Exception { } @Test - void executeQueryAppliesQueryHints() throws Exception { + void executeQueryAppliesQueryHints() { final AbstractQuery q = createQuery(SELECT_QUERY, OWLClassA.class); final String hintName = "jopa.query.testHint"; final QueryHintsHandler.Hint hint = spy(new TestHint(hintName)); QueryHintsHandler.Hint.registerHint(hint); q.setHint(hintName, true); - q.executeQuery((r) -> {}); + q.getResultList(); verify(hint).apply(true, q, statementMock); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/TypedQueryImplTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/TypedQueryImplTest.java index 348ae656c..12f909c8e 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/TypedQueryImplTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/TypedQueryImplTest.java @@ -34,6 +34,7 @@ import cz.cvut.kbss.jopa.query.QueryHints; import cz.cvut.kbss.jopa.query.QueryParameter; import cz.cvut.kbss.jopa.query.parameter.ParameterValueFactory; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; import cz.cvut.kbss.jopa.vocabulary.RDF; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import org.junit.jupiter.api.BeforeEach; @@ -458,7 +459,8 @@ void optimizeEntityResultLoadingHintAppliesEntityLoadingOptimizer() throws Excep when(resultRow.getObject(1, URI.class)).thenReturn(URI.create(RDF.TYPE)); when(resultRow.getObject(2)).thenReturn(URI.create(Vocabulary.c_OwlClassB)); when(resultRow.getColumnCount()).thenReturn(3); - when(uowMock.readObjectFromAxioms(eq(OWLClassB.class), anyCollection(), any())).thenReturn(entity); + when(uowMock.readObjectFromAxioms(eq(OWLClassB.class), anyCollection(), + eq(new AxiomBasedLoadingConfigGroup<>(entity.getUri(), new EntityDescriptor())))).thenReturn(entity); final MetamodelImpl metamodel = mock(MetamodelImpl.class); when(uowMock.getMetamodel()).thenReturn(metamodel); when(uowMock.isEntityType(OWLClassB.class)).thenReturn(true); @@ -474,6 +476,10 @@ void optimizeEntityResultLoadingHintAppliesEntityLoadingOptimizer() throws Excep @Test void getResultListIsAbleToMapProjectedVariablesToEntityAttributes() throws Exception { final OWLClassA entity = Generators.generateOwlClassAInstance(); + final MetamodelImpl metamodel = mock(MetamodelImpl.class); + when(uowMock.getMetamodel()).thenReturn(metamodel); + final MetamodelMocks mocks = new MetamodelMocks(); + mocks.setMocks(metamodel); final TypedQuery sut = create("SELECT ?x ?stringAttribute ?types WHERE {" + "?x a " + stringifyIri(Vocabulary.c_OwlClassA) + " ;" + stringifyIri(Vocabulary.p_a_stringAttribute) + " ?stringAttribute ;" + @@ -483,16 +489,15 @@ void getResultListIsAbleToMapProjectedVariablesToEntityAttributes() throws Excep when(resultSetIterator.next()).thenReturn(resultRow); when(resultRow.getColumnNames()).thenReturn(List.of("x", "stringAttribute", "types")); when(resultRow.getObject(0, URI.class)).thenReturn(entity.getUri()); + when(resultRow.isBound("x")).thenReturn(true); + when(resultRow.getObject("x", URI.class)).thenReturn(entity.getUri()); when(resultRow.isBound("stringAttribute")).thenReturn(true); when(resultRow.getObject("stringAttribute")).thenReturn(entity.getStringAttribute()); when(resultRow.isBound("types")).thenReturn(true); - when(resultRow.getObject("types")).thenReturn(URI.create(Vocabulary.c_OwlClassA)); + when(resultRow.getString("types")).thenReturn(Vocabulary.c_OwlClassA); when(resultRow.getColumnCount()).thenReturn(3); - when(uowMock.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any())).thenReturn(entity); - final MetamodelImpl metamodel = mock(MetamodelImpl.class); - when(uowMock.getMetamodel()).thenReturn(metamodel); - final MetamodelMocks mocks = new MetamodelMocks(); - mocks.setMocks(metamodel); + when(uowMock.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), + eq(new AxiomBasedLoadingConfigGroup<>(entity.getUri(), new EntityDescriptor())))).thenReturn(entity); final List result = sut.getResultList(); assertEquals(List.of(entity), result); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/DataPropertyFieldStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/DataPropertyFieldStrategyTest.java index 8b6ebc1fc..31648b149 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/DataPropertyFieldStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/DataPropertyFieldStrategyTest.java @@ -21,6 +21,7 @@ import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -56,7 +57,7 @@ void setUp() throws Exception { void getLanguagesUsesAttributeConfiguredLanguageWhenDescriptorDoesNotSpecifyAny() { final DataPropertyFieldStrategy sut = (DataPropertyFieldStrategy) FieldStrategy .createFieldStrategy(metamodelMocks.forOwlClassA().entityType(), - metamodelMocks.forOwlClassA().stringAttribute(), descriptor, mapperMock); + metamodelMocks.forOwlClassA().stringAttribute(), new ObjectGraphInfo(descriptor), mapperMock); assertTrue(metamodelMocks.forOwlClassA().stringAttribute().hasLanguage()); assertEquals(Generators.LANG, sut.getLanguage()); } @@ -67,7 +68,7 @@ void getLanguageRetrievesAttributeLanguageFromEntityDescriptor() { descriptor.setAttributeLanguage(metamodelMocks.forOwlClassA().stringAttribute(), lang); final DataPropertyFieldStrategy sut = (DataPropertyFieldStrategy) FieldStrategy .createFieldStrategy(metamodelMocks.forOwlClassA().entityType(), - metamodelMocks.forOwlClassA().stringAttribute(), descriptor, mapperMock); + metamodelMocks.forOwlClassA().stringAttribute(), new ObjectGraphInfo(descriptor), mapperMock); assertEquals(lang, sut.getLanguage()); } @@ -75,7 +76,7 @@ void getLanguageRetrievesAttributeLanguageFromEntityDescriptor() { void getLanguageReturnsNullForSimpleLiteralAttribute() { final DataPropertyFieldStrategy sut = (DataPropertyFieldStrategy) FieldStrategy .createFieldStrategy(metamodelMocks.forOwlClassM().entityType(), - metamodelMocks.forOwlClassM().simpleLiteralAttribute(), descriptor, mapperMock); + metamodelMocks.forOwlClassM().simpleLiteralAttribute(), new ObjectGraphInfo(descriptor), mapperMock); assertNull(sut.getLanguage()); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/DefaultInstanceLoaderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/DefaultInstanceLoaderTest.java index 7272cc8fa..9b545cf66 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/DefaultInstanceLoaderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/DefaultInstanceLoaderTest.java @@ -24,6 +24,7 @@ import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; @@ -101,7 +102,8 @@ void setUp() throws Exception { void testLoadEntity() throws Exception { final Collection> entityAAxioms = Generators.generateAxiomsForOWLClassA(IDENTIFIER); when(connectionMock.find(axiomDescriptor)).thenReturn(entityAAxioms); - when(entityConstructorMock.reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, etAMock, descriptor, false), entityAAxioms)) + when(entityConstructorMock.reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, etAMock, descriptor, new FetchGraphWrapper(), + false), entityAAxioms)) .thenReturn(entityA); final OWLClassA res = instanceLoader.loadEntity(loadingParameters); @@ -132,7 +134,7 @@ void loadEntityBypassesCacheWhenConfiguredTo() throws Exception { .thenReturn(axiomDescriptor); final Collection> entityAAxioms = Generators.generateAxiomsForOWLClassA(IDENTIFIER); when(connectionMock.find(axiomDescriptor)).thenReturn(entityAAxioms); - when(entityConstructorMock.reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, etAMock, descriptor, false), entityAAxioms)) + when(entityConstructorMock.reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, etAMock, descriptor, new FetchGraphWrapper(), false), entityAAxioms)) .thenReturn(entityA); final OWLClassA res = instanceLoader.loadEntity(loadingParameters); assertNotNull(res); @@ -147,7 +149,7 @@ void loadEntityReloadsQueryAttributesWhenInstanceIsRetrievedFromCache() { final OWLClassA res = instanceLoader.loadEntity(loadingParameters); assertEquals(entityA, res); verify(entityConstructorMock).populateQueryAttributes(entityA, etAMock); - verify(entityConstructorMock, never()).reconstructEntity(eq(new EntityConstructor.EntityConstructionParameters<>(loadingParameters.identifier(), etAMock, descriptor, false)), anyCollection()); + verify(entityConstructorMock, never()).reconstructEntity(eq(new EntityConstructor.EntityConstructionParameters<>(loadingParameters.identifier(), etAMock, descriptor, new FetchGraphWrapper(), false)), anyCollection()); } @Test diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorPluralAttributesTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorPluralAttributesTest.java index 5dc81289b..18cec4ca3 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorPluralAttributesTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorPluralAttributesTest.java @@ -24,8 +24,10 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.ListAttribute; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory; import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptor; @@ -111,7 +113,8 @@ public void reconstructsEntityWithSimpleList() { final OWLClassC res = constructor .reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(ID, metamodelMocks.forOwlClassC() - .entityType(), descriptor, true), + .entityType(), descriptor, new FetchGraphWrapper(), + true), axioms); assertNotNull(res); @@ -123,7 +126,7 @@ public void reconstructsEntityWithSimpleList() { private void prepareMapperMockForSimpleListLoad() { for (Entry e : LIST_CONTENT.entrySet()) { - when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(e.getKey()), any(Descriptor.class))).thenReturn(e.getValue()); + when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(e.getKey()), any(ObjectGraphInfo.class))).thenReturn(e.getValue()); } final Collection> listAxioms = initSimpleListAxioms(); when(mapperMock.loadSimpleList(any(SimpleListDescriptor.class))).thenReturn(listAxioms); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorQueryAttributesTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorQueryAttributesTest.java index 22144f00e..cb9553147 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorQueryAttributesTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorQueryAttributesTest.java @@ -26,6 +26,7 @@ import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory; import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; @@ -141,7 +142,7 @@ void testReconstructEntityWithManagedTypeQueryAttribute() throws Exception { entityA.setUri(identifierTwo); entityA.setStringAttribute(stringValue); - when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, identifierTwo, fieldDesc)) + when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, identifierTwo, new ObjectGraphInfo(fieldDesc))) .thenReturn(entityA); doReturn(typedQueryMock) @@ -155,7 +156,7 @@ void testReconstructEntityWithManagedTypeQueryAttribute() throws Exception { .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(IDENTIFIER, res.getUri()); - verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, identifierTwo, fieldDesc); + verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, identifierTwo, new ObjectGraphInfo(fieldDesc)); assertNotNull(res.getEntityAttribute()); assertEquals(identifierTwo, res.getEntityAttribute().getUri()); assertEquals(stringValue, res.getEntityAttribute().getStringAttribute()); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorTest.java index 3a8147aaa..cab9cf088 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/EntityConstructorTest.java @@ -31,8 +31,11 @@ import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.exceptions.CardinalityConstraintViolatedException; import cz.cvut.kbss.jopa.exceptions.IntegrityConstraintViolatedException; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; import cz.cvut.kbss.jopa.model.LoadState; +import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.SequencesVocabulary; import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty; @@ -42,9 +45,11 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory; import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.jopa.vocabulary.DC; @@ -75,6 +80,7 @@ import java.util.Map.Entry; import java.util.Set; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -117,6 +123,9 @@ void setUp() throws Exception { when(mapperMock.getUow()).thenReturn(uowMock); when(uowMock.sparqlQueryFactory()).thenReturn(queryFactoryMock); this.mocks = new MetamodelMocks(); + final MetamodelImpl metamodel = mock(MetamodelImpl.class); + mocks.setMocks(metamodel); + when(uowMock.getMetamodel()).thenReturn(metamodel); this.descriptor = new EntityDescriptor(); this.constructor = new EntityConstructor(mapperMock, loadStateRegistry); } @@ -168,7 +177,7 @@ private Set> getTypesAxiomsForOwlClassA() { static EntityConstructor.EntityConstructionParameters constructionConfig(URI id, IdentifiableEntityType et, Descriptor descriptor) { - return new EntityConstructor.EntityConstructionParameters<>(id, et, descriptor, false); + return new EntityConstructor.EntityConstructionParameters<>(id, et, descriptor, new FetchGraphWrapper(), false); } @Test @@ -250,12 +259,12 @@ void testReconstructEntityWithObjectProperty() { final OWLClassA entityA = new OWLClassA(); entityA.setUri(ID_TWO); entityA.setStringAttribute(STRING_ATT); - when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, fieldDesc)).thenReturn(entityA); + when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, new ObjectGraphInfo(fieldDesc))).thenReturn(entityA); final OWLClassD res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassD() .entityType(), descriptor), axiomsD); assertNotNull(res); assertEquals(ID, res.getUri()); - verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, fieldDesc); + verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, new ObjectGraphInfo(fieldDesc)); assertNotNull(res.getOwlClassA()); assertEquals(ID_TWO, res.getOwlClassA().getUri()); assertEquals(STRING_ATT, res.getOwlClassA().getStringAttribute()); @@ -327,7 +336,7 @@ void reconstructsEntityWithObjectPropertyWhereRangeDoesNotMatch() { final Descriptor fieldDescriptor = new EntityDescriptor(); descriptor.addAttributeDescriptor(mocks.forOwlClassD().owlClassAAtt(), fieldDescriptor); // The property value hasn't the corresponding type, so it cannot be returned by mapper - when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, fieldDescriptor)).thenReturn(null); + when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, new ObjectGraphInfo(fieldDescriptor))).thenReturn(null); final OWLClassD res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassD() .entityType(), descriptor), axioms); assertNotNull(res); @@ -346,8 +355,8 @@ void throwsExceptionWhenObjectPropertyCardinalityRestrictionIsNotMet() { entityThree.setUri(pkThree); final Descriptor fieldDescriptor = new EntityDescriptor(); descriptor.addAttributeDescriptor(mocks.forOwlClassD().owlClassAAtt(), fieldDescriptor); - when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, fieldDescriptor)).thenReturn(entityA); - when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, pkThree, fieldDescriptor)) + when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, new ObjectGraphInfo(fieldDescriptor))).thenReturn(entityA); + when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, pkThree, new ObjectGraphInfo(fieldDescriptor))) .thenReturn(entityThree); assertThrows(CardinalityConstraintViolatedException.class, () -> constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassD() @@ -371,8 +380,8 @@ void testSetFieldValue_DataProperty() throws Exception { @Test void testSetFieldValue_ObjectProperty() { final Set> axioms = new HashSet<>(); - final EntityDescriptor fieldDesc = mock(EntityDescriptor.class); - descriptor.addAttributeDescriptor(mocks.forOwlClassD().owlClassAAtt(), fieldDesc); + final EntityDescriptor fieldDescriptor = mock(EntityDescriptor.class); + descriptor.addAttributeDescriptor(mocks.forOwlClassD().owlClassAAtt(), fieldDescriptor); axioms.add(createObjectPropertyAxiomForD()); final OWLClassD entityD = new OWLClassD(); entityD.setUri(ID); @@ -380,14 +389,14 @@ void testSetFieldValue_ObjectProperty() { final OWLClassA entityA = new OWLClassA(); entityA.setUri(ID_TWO); entityA.setStringAttribute(STRING_ATT); - when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, fieldDesc)) + when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, new ObjectGraphInfo(fieldDescriptor))) .thenReturn(entityA); constructor.setFieldValue(entityD, mocks.forOwlClassD().owlClassAAtt(), axioms, mocks.forOwlClassD().entityType(), descriptor); assertNotNull(entityD.getOwlClassA()); assertEquals(ID_TWO, entityD.getOwlClassA().getUri()); - verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, fieldDesc); + verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, new ObjectGraphInfo(fieldDescriptor)); } @Test @@ -454,7 +463,7 @@ private Set initEntities(Descriptor desc) { a.setUri(URI.create("http://krizik.felk.cvut.cz/entityA" + i)); a.setStringAttribute(STRING_ATT); entities.add(a); - when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, a.getUri(), desc)) + when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, a.getUri(), new ObjectGraphInfo(desc))) .thenReturn(a); } return entities; @@ -513,7 +522,8 @@ void reconstructsEntityWithDataPropertiesOfBasicTypesAndStringIdentifier() throw assertEquals(character, res.getCharacterAttribute()); } - private Collection> createAxiomsForValues(Boolean b, Integer i, Long lng, Double d, Date date, Character character, + private Collection> createAxiomsForValues(Boolean b, Integer i, Long lng, Double d, Date date, + Character character, OWLClassM.Severity severity) throws Exception { final Collection> axioms = new ArrayList<>(); @@ -546,7 +556,8 @@ private Collection> createAxiomsForValues(Boolean b, Integer i, Long ln new Value<>(date))); } if (character != null) { - final String characterAttIri = OWLClassM.getCharacterAttributeField().getAnnotation(OWLDataProperty.class).iri(); + final String characterAttIri = OWLClassM.getCharacterAttributeField().getAnnotation(OWLDataProperty.class) + .iri(); axioms.add( new AxiomImpl<>(ID_RESOURCE, Assertion.createDataPropertyAssertion(URI.create(characterAttIri), false), new Value<>(character.toString()))); @@ -622,7 +633,7 @@ private Set> initInvalidFieldValuesForICValidation() throws NoSuchField for (int i = 0; i < max + 1; i++) { final URI uri = URI.create("http://value" + i); listAxioms.add(new AxiomImpl<>(ID_RESOURCE, nodeContent, new Value<>(NamedResource.create(uri)))); - when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, uri, attDescriptor)) + when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, uri, new ObjectGraphInfo(attDescriptor))) .thenReturn(new OWLClassA(uri)); } final Assertion assertion = Assertion.createObjectPropertyAssertion( @@ -676,7 +687,7 @@ void reconstructsInstanceWithMappedSuperclass() throws Exception { new Value<>(NamedResource.create(ID_TWO)))); final OWLClassA a = new OWLClassA(); a.setUri(ID_TWO); - when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(ID_TWO), any(Descriptor.class))) + when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(ID_TWO), any(ObjectGraphInfo.class))) .thenReturn(a); final OWLClassQ res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassQ() @@ -807,10 +818,10 @@ void reconstructEntityLoadsLazyReferencesWhenForceEagerIsSpecifiedForReconstruct getClassAssertionAxiomForType(ID, OWLClassD.getClassIri()), new AxiomImpl<>(NamedResource.create(ID), getClassDObjectPropertyAssertion(), new Value<>(NamedResource.create(aInstance.getUri()))) ); - when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aInstance.getUri()), any(Descriptor.class))).thenReturn(aInstance); + when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aInstance.getUri()), any(ObjectGraphInfo.class))).thenReturn(aInstance); final OWLClassD result = constructor.reconstructEntity( new EntityConstructor.EntityConstructionParameters<>(ID, mocks.forOwlClassD() - .entityType(), descriptor, true), axioms); + .entityType(), descriptor, new FetchGraphWrapper(), true), axioms); assertNotNull(result); assertNotNull(result.getOwlClassA()); assertEquals(aInstance, result.getOwlClassA()); @@ -818,4 +829,42 @@ void reconstructEntityLoadsLazyReferencesWhenForceEagerIsSpecifiedForReconstruct assertNotNull(loadStates); assertEquals(LoadState.LOADED, loadStates.isLoaded(mocks.forOwlClassD().owlClassAAtt())); } + + @Test + void reconstructEntityMarksLazilyLoadedAttributeAsLoadedWhenNoAxiomIsAvailableForItAndItIsSpecifiedInFetchGraph() { + when(mocks.forOwlClassD().owlClassAAtt().getFetchType()).thenReturn(FetchType.LAZY); + final List> axioms = List.of( + getClassAssertionAxiomForType(ID, OWLClassD.getClassIri()), + new AxiomImpl<>(NamedResource.create(ID), Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false), new Value<>(NamedResource.create(Generators.createIndividualIdentifier()))) + ); + final EntityGraph fetchGraph = new EntityGraphImpl<>(mocks.forOwlClassD() + .entityType(), uowMock.getMetamodel()); + fetchGraph.addAttributeNodes("owlClassA"); + final EntityConstructor.EntityConstructionParameters constructionParams = new EntityConstructor.EntityConstructionParameters<>(ID, mocks.forOwlClassD() + .entityType(), descriptor, new FetchGraphWrapper(fetchGraph), false); + final OWLClassD result = constructor.reconstructEntity(constructionParams, axioms); + assertNull(result.getOwlClassA()); + final LoadStateDescriptor loadStates = loadStateRegistry.get(result); + assertNotNull(loadStates); + assertEquals(LoadState.LOADED, loadStates.isLoaded(mocks.forOwlClassD().owlClassAAtt())); + } + + @Test + void reconstructEntityValidatesIntegrityConstraintsOnlyOnLoadedAttributes() { + final OWLClassA aInstance = Generators.generateOwlClassAInstance(); + final List> axioms = List.of( + getClassAssertionAxiomForType(ID, OWLClassL.getClassIri()), + new AxiomImpl<>(NamedResource.create(ID), + Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false), + new Value<>(NamedResource.create(aInstance.getUri()))) + ); + when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aInstance.getUri()), any())).thenReturn(aInstance); + final EntityGraph fetchGraph = new EntityGraphImpl<>(mocks.forOwlClassL() + .entityType(), uowMock.getMetamodel()); + fetchGraph.addAttributeNodes("set"); + final EntityConstructor.EntityConstructionParameters constructionParams = new EntityConstructor.EntityConstructionParameters<>(ID, mocks.forOwlClassL() + .entityType(), descriptor, new FetchGraphWrapper(fetchGraph), false); + assertDoesNotThrow(() -> constructor.reconstructEntity(constructionParams, axioms)); + + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/FieldStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/FieldStrategyTest.java index 73e0aa0ae..733ca6a60 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/FieldStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/FieldStrategyTest.java @@ -24,6 +24,7 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.*; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -63,7 +64,7 @@ void getAttributeContextRetrievesContextFromEntityDescriptor() { final URI context = Generators.createIndividualIdentifier(); descriptor.addAttributeContext(metamodelMocks.forOwlClassA().stringAttribute(), context); final FieldStrategy sut = FieldStrategy.createFieldStrategy(metamodelMocks.forOwlClassA().entityType(), - metamodelMocks.forOwlClassA().stringAttribute(), descriptor, mapperMock); + metamodelMocks.forOwlClassA().stringAttribute(), new ObjectGraphInfo(descriptor), mapperMock); assertEquals(context, sut.getAttributeWriteContext()); verify(descriptor).getSingleAttributeContext(metamodelMocks.forOwlClassA().stringAttribute()); } @@ -78,7 +79,7 @@ void createFieldStrategyCreatesPluralDataPropertyStrategyForDataPropertyCollecti when(att.getBindableJavaType()).thenReturn(String.class); when(att.getElementType()).thenReturn(BasicTypeImpl.get(String.class)); when(att.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.DATA); - final FieldStrategy result = FieldStrategy.createFieldStrategy(et, att, descriptor, mapperMock); + final FieldStrategy result = FieldStrategy.createFieldStrategy(et, att, new ObjectGraphInfo(descriptor), mapperMock); assertThat(result, instanceOf(PluralDataPropertyStrategy.class)); } @@ -92,14 +93,14 @@ void createFieldStrategyCreatesSimpleSetStrategyForObjectPropertyCollectionAttri when(att.getBindableJavaType()).thenReturn(OWLClassA.class); when(att.getElementType()).thenReturn(metamodelMocks.forOwlClassA().entityType()); when(att.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.OBJECT); - final FieldStrategy result = FieldStrategy.createFieldStrategy(et, att, descriptor, mapperMock); + final FieldStrategy result = FieldStrategy.createFieldStrategy(et, att, new ObjectGraphInfo(descriptor), mapperMock); assertThat(result, instanceOf(SimpleSetPropertyStrategy.class)); } @Test void createFieldStrategyCreatesSingularMultilingualStringFieldStrategyForSingularMultilingualStringAttributeMappingDataProperty() { final FieldStrategy result = FieldStrategy.createFieldStrategy(metamodelMocks.forOwlClassU().entityType(), - metamodelMocks.forOwlClassU().uSingularStringAtt(), descriptor, mapperMock); + metamodelMocks.forOwlClassU().uSingularStringAtt(), new ObjectGraphInfo(descriptor), mapperMock); assertThat(result, instanceOf(SingularMultilingualStringFieldStrategy.class)); } @@ -108,7 +109,7 @@ void createFieldStrategyCreatesSingularMultilingualStringFieldStrategyForSingula final AbstractAttribute att = metamodelMocks.forOwlClassU().uSingularStringAtt(); when(att.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.ANNOTATION); final FieldStrategy result = FieldStrategy.createFieldStrategy(metamodelMocks.forOwlClassU().entityType(), - metamodelMocks.forOwlClassU().uSingularStringAtt(), descriptor, mapperMock); + metamodelMocks.forOwlClassU().uSingularStringAtt(), new ObjectGraphInfo(descriptor), mapperMock); assertThat(result, instanceOf(SingularMultilingualStringFieldStrategy.class)); } @@ -122,7 +123,7 @@ void createFieldStrategyCreatesReferencedListDataPropertyStrategyForDataProperty when(att.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.DATA); when(att.getElementType()).thenReturn(BasicTypeImpl.get(LocalDate.class)); - final FieldStrategy result = FieldStrategy.createFieldStrategy(et, att, descriptor, mapperMock); + final FieldStrategy result = FieldStrategy.createFieldStrategy(et, att, new ObjectGraphInfo(descriptor), mapperMock); assertThat(result, instanceOf(ReferencedListDataPropertyStrategy.class)); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ObjectOntologicalMappingTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ObjectOntologicalMappingTest.java new file mode 100644 index 000000000..8dcd15b80 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ObjectOntologicalMappingTest.java @@ -0,0 +1,165 @@ +package cz.cvut.kbss.jopa.oom; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.Person; +import cz.cvut.kbss.jopa.environment.Phone; +import cz.cvut.kbss.jopa.environment.Vocabulary; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; +import cz.cvut.kbss.jopa.model.LoadState; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.Subgraph; +import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.sessions.AbstractUnitOfWork; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingParameters; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; +import cz.cvut.kbss.jopa.utils.Configuration; +import cz.cvut.kbss.ontodriver.Connection; +import cz.cvut.kbss.ontodriver.model.Assertion; +import cz.cvut.kbss.ontodriver.model.Axiom; +import cz.cvut.kbss.ontodriver.model.AxiomImpl; +import cz.cvut.kbss.ontodriver.model.NamedResource; +import cz.cvut.kbss.ontodriver.model.Value; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasProperty; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class ObjectOntologicalMappingTest { + + @Mock + private Connection storageConnection; + + @Mock + private CacheManager cacheManager; + + @Mock + private AbstractUnitOfWork uow; + + @Mock + private MetamodelImpl metamodel; + + private ObjectOntologyMapperImpl sut; + + @BeforeEach + public void setUp() throws Exception { + final MetamodelMocks metamodelMocks = new MetamodelMocks(); + metamodelMocks.setMocks(metamodel); + when(uow.getMetamodel()).thenReturn(metamodel); + when(uow.getLiveObjectCache()).thenReturn(cacheManager); + when(uow.getLoadStateRegistry()).thenReturn(new LoadStateDescriptorRegistry(Object::toString)); + when(uow.getConfiguration()).thenReturn(new Configuration()); + this.sut = new ObjectOntologyMapperImpl(uow, storageConnection); + } + + @Test + void loadEntityFromAxiomsLoadsEntityGraphWhenAxiomsForMultipleRelatedEntitiesAreSupplied() { + final OWLClassD expected = new OWLClassD(Generators.createIndividualIdentifier()); + final OWLClassA aInstance = Generators.generateOwlClassAInstance(); + expected.setOwlClassA(aInstance); + final List> axioms = instanceAxioms(expected); + + final OWLClassD result = sut.loadEntity(new AxiomBasedLoadingParameters<>(OWLClassD.class, axioms, new AxiomBasedLoadingConfigGroup<>(expected.getUri(), new EntityDescriptor()))); + assertEquals(expected.getUri(), result.getUri()); + assertNotNull(result.getOwlClassA()); + assertThat(result.getOwlClassA(), allOf( + hasProperty("uri", equalTo(aInstance.getUri())), + hasProperty("stringAttribute", equalTo(aInstance.getStringAttribute())), + hasProperty("types", equalTo(aInstance.getTypes())) + )); + } + + private static List> instanceAxioms(OWLClassD d) { + final List> axioms = new ArrayList<>(); + final NamedResource dIndividual = NamedResource.create(d.getUri()); + axioms.add(new AxiomImpl<>(dIndividual, Assertion.createClassAssertion(false), new Value<>(URI.create(Vocabulary.c_OwlClassD)))); + axioms.add(new AxiomImpl<>(dIndividual, Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_h_hasA), false), new Value<>(d.getOwlClassA() + .getUri()))); + final NamedResource aIndividual = NamedResource.create(d.getOwlClassA().getUri()); + axioms.add(new AxiomImpl<>(aIndividual, Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_a_stringAttribute), false), new Value<>(d.getOwlClassA() + .getStringAttribute()))); + axioms.add(new AxiomImpl<>(aIndividual, Assertion.createClassAssertion(false), new Value<>(URI.create(Vocabulary.c_OwlClassA)))); + d.getOwlClassA().getTypes() + .forEach(type -> axioms.add(new AxiomImpl<>(aIndividual, Assertion.createClassAssertion(false), new Value<>(URI.create(type))))); + return axioms; + } + + @Test + void loadEntityFromAxiomsLoadsEntityWithRelatedEntitiesAndPopulatesLoadStateDescriptorBasedOnFetchGraph() { + final Person expected = new Person(Generators.createIndividualIdentifier()); + expected.setFirstName("John"); + expected.setLastName("Doe"); + expected.setPhone(new Phone(Generators.createIndividualIdentifier(), "123456789", "Nokia")); + final List> axioms = instanceAxioms(expected); + + final IdentifiableEntityType et = metamodel.entity(Person.class); + final EntityGraph fetchGraph = new EntityGraphImpl<>(et, metamodel); + // Age is not in the data + fetchGraph.addAttributeNodes("firstName", "lastName", "age", "phone"); + final Subgraph subgraph = fetchGraph.addSubgraph("phone"); + subgraph.addAttributeNodes("number", "brand"); + final Person result = sut.loadEntity(new AxiomBasedLoadingParameters<>(Person.class, axioms, new AxiomBasedLoadingConfigGroup<>(expected.getUri(), new EntityDescriptor(), fetchGraph))); + assertEquals(expected.getUri(), result.getUri()); + assertEquals(expected.getFirstName(), result.getFirstName()); + assertEquals(expected.getLastName(), result.getLastName()); + assertNull(result.getAge()); + assertNotNull(result.getPhone()); + assertEquals(expected.getPhone().getUri(), result.getPhone().getUri()); + assertEquals(expected.getPhone().getNumber(), result.getPhone().getNumber()); + assertEquals(expected.getPhone().getBrand(), result.getPhone().getBrand()); + final LoadStateDescriptor personLoadState = uow.getLoadStateRegistry().get(result); + assertNotNull(personLoadState); + assertEquals(LoadState.LOADED, personLoadState.isLoaded(et.getAttribute("firstName"))); + assertEquals(LoadState.LOADED, personLoadState.isLoaded(et.getAttribute("lastName"))); + assertEquals(LoadState.LOADED, personLoadState.isLoaded(et.getAttribute("age"))); + assertEquals(LoadState.LOADED, personLoadState.isLoaded(et.getAttribute("phone"))); + assertEquals(LoadState.UNKNOWN, personLoadState.isLoaded(et.getAttribute("username"))); + assertEquals(LoadState.UNKNOWN, personLoadState.isLoaded(et.getAttribute("gender"))); + final LoadStateDescriptor phoneLoadState = uow.getLoadStateRegistry().get(result.getPhone()); + assertNotNull(phoneLoadState); + assertEquals(LoadState.LOADED, phoneLoadState.isLoaded(metamodel.entity(Phone.class).getAttribute("number"))); + assertEquals(LoadState.LOADED, phoneLoadState.isLoaded(metamodel.entity(Phone.class).getAttribute("brand"))); + } + + private static List> instanceAxioms(Person person) { + final List> axioms = new ArrayList<>(); + final NamedResource pIndividual = NamedResource.create(person.getUri()); + axioms.add(new AxiomImpl<>(pIndividual, Assertion.createClassAssertion(false), new Value<>(URI.create(Vocabulary.c_Person)))); + axioms.add(new AxiomImpl<>(pIndividual, Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_p_firstName), false), new Value<>(person.getFirstName()))); + axioms.add(new AxiomImpl<>(pIndividual, Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_p_lastName), false), new Value<>(person.getLastName()))); + final NamedResource phoneIndividual = NamedResource.create(person.getPhone().getUri()); + axioms.add(new AxiomImpl<>(pIndividual, Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_p_hasPhone), false), new Value<>(person.getPhone() + .getUri()))); + axioms.add(new AxiomImpl<>(phoneIndividual, Assertion.createClassAssertion(false), new Value<>(URI.create(Vocabulary.c_Phone)))); + axioms.add(new AxiomImpl<>(phoneIndividual, Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_p_phoneNumber), false), new Value<>(person.getPhone() + .getNumber()))); + axioms.add(new AxiomImpl<>(phoneIndividual, Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_p_phoneBrand), false), new Value<>(person.getPhone() + .getBrand()))); + return axioms; + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperTest.java index 680f0a914..db2433226 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperTest.java @@ -38,12 +38,14 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.oom.exception.UnpersistedChangeException; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.proxy.reference.EntityReferenceProxy; import cz.cvut.kbss.jopa.proxy.reference.EntityReferenceProxyGenerator; import cz.cvut.kbss.jopa.sessions.AbstractUnitOfWork; import cz.cvut.kbss.jopa.sessions.cache.CacheManager; import cz.cvut.kbss.jopa.sessions.cache.Descriptors; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingParameters; import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; @@ -239,7 +241,7 @@ void testGetEntityFromCacheOrOntologyFromCache() { final LoadStateDescriptor loadStateDescriptor = new LoadStateDescriptor<>(entityA, mocks.forOwlClassA() .entityType(), LoadState.UNKNOWN); doReturn(loadStateDescriptor).when(cacheMock).getLoadStateDescriptor(entityA); - final OWLClassA res = mapper.getEntityFromCacheOrOntology(OWLClassA.class, IDENTIFIER, aDescriptor); + final OWLClassA res = mapper.getEntityFromCacheOrOntology(OWLClassA.class, IDENTIFIER, new ObjectGraphInfo(aDescriptor)); assertNotNull(res); assertSame(entityA, res); verify(cacheMock).get(OWLClassA.class, IDENTIFIER, aDescriptor); @@ -249,8 +251,7 @@ void testGetEntityFromCacheOrOntologyFromCache() { void testGetEntityFromCacheOrOntologyFromRegisteredInstances() { when(cacheMock.contains(OWLClassA.class, IDENTIFIER, null)).thenReturn(Boolean.FALSE); mapper.registerInstance(IDENTIFIER, entityA); - final OWLClassA res = mapper.getEntityFromCacheOrOntology(OWLClassA.class, IDENTIFIER, - aDescriptor); + final OWLClassA res = mapper.getEntityFromCacheOrOntology(OWLClassA.class, IDENTIFIER, new ObjectGraphInfo(aDescriptor)); assertNotNull(res); assertSame(entityA, res); } @@ -267,7 +268,7 @@ void getEntityFromCacheOrOntologyLoadsEntityWhenItIsNotInCache() throws Exceptio final LoadStateDescriptor loadStateDescriptor = new LoadStateDescriptor<>(entityA, mocks.forOwlClassA() .entityType(), LoadState.UNKNOWN); loadStateRegistry.put(entityA, loadStateDescriptor); - final OWLClassA res = mapper.getEntityFromCacheOrOntology(OWLClassA.class, IDENTIFIER, aDescriptor); + final OWLClassA res = mapper.getEntityFromCacheOrOntology(OWLClassA.class, IDENTIFIER, new ObjectGraphInfo(aDescriptor)); assertSame(entityA, res); verify(loader).loadEntity(loadingParameters); } @@ -630,8 +631,8 @@ void persistSavesPendingReferencedListContainingPersistedInstance() throws Excep Assertion.createObjectPropertyAssertion(URI.create(SequencesVocabulary.s_p_hasNext), false); final Assertion valueProp = Assertion.createObjectPropertyAssertion(URI.create(SequencesVocabulary.s_p_hasContents), false); - final ReferencedListValueDescriptor listDesc = - new ReferencedListValueDescriptor(ID_RESOURCE, listProp, nextProp, valueProp); + final ReferencedListValueDescriptor listDesc = + new ReferencedListValueDescriptor<>(ID_RESOURCE, listProp, nextProp, valueProp); mapper.registerPendingListReference(pending, listDesc, owner.getReferencedList()); when(entityDeconstructorMock.mapEntityToAxioms(pending.getUri(), pending, etAMock, aDescriptor)) @@ -647,7 +648,7 @@ void persistSavesPendingReferencedListContainingPersistedInstance() throws Excep void getEntityFromCacheOrOntologyThrowsEntityExistsWhenObjectIsAlreadyRegisteredUnderDifferentType() { mapper.registerInstance(IDENTIFIER, entityA); assertThrows(OWLEntityExistsException.class, - () -> mapper.getEntityFromCacheOrOntology(OWLClassB.class, IDENTIFIER, aDescriptor)); + () -> mapper.getEntityFromCacheOrOntology(OWLClassB.class, IDENTIFIER, new ObjectGraphInfo(aDescriptor))); } @Test @@ -701,7 +702,7 @@ void loadEntityFromAxiomsReconstructsEntityFromAxiomsAndReturnsIt() { new AxiomImpl<>(ID_RESOURCE, Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_a_stringAttribute), false), new Value<>("value")) ); - final OWLClassA result = mapper.loadEntity(new AxiomBasedLoadingParameters<>(OWLClassA.class, aDescriptor, false, axioms)); + final OWLClassA result = mapper.loadEntity(new AxiomBasedLoadingParameters<>(OWLClassA.class, axioms, new AxiomBasedLoadingConfigGroup<>(IDENTIFIER, aDescriptor, null))); assertNotNull(result); assertEquals(IDENTIFIER, result.getUri()); assertEquals("value", result.getStringAttribute()); @@ -710,18 +711,19 @@ void loadEntityFromAxiomsReconstructsEntityFromAxiomsAndReturnsIt() { @Test void loadEntityFromAxiomsReturnsNullWhenAxiomsAreEmpty() { - assertNull(mapper.loadEntity(new AxiomBasedLoadingParameters<>(OWLClassA.class, aDescriptor, false, List.of()))); + assertNull(mapper.loadEntity(new AxiomBasedLoadingParameters<>(OWLClassA.class, List.of(), new AxiomBasedLoadingConfigGroup<>(IDENTIFIER, aDescriptor)))); } @Test - void loadEntityFromAxiomsPutsLoadedEntityToCache() { + void loadEntityFromAxiomsDoesNotPutLoadedEntityToCache() { final List> axioms = List.of( new AxiomImpl<>(ID_RESOURCE, Assertion.createClassAssertion(false), new Value<>(URI.create(OWLClassA.getClassIri()))), new AxiomImpl<>(ID_RESOURCE, Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_a_stringAttribute), false), new Value<>("value")) ); - final OWLClassA result = mapper.loadEntity(new AxiomBasedLoadingParameters<>(OWLClassA.class, aDescriptor, false, axioms)); - verify(cacheMock).add(IDENTIFIER, result, new Descriptors(aDescriptor, loadStateRegistry.get(result))); + final OWLClassA result = mapper.loadEntity(new AxiomBasedLoadingParameters<>(OWLClassA.class, axioms, new AxiomBasedLoadingConfigGroup<>(IDENTIFIER, aDescriptor))); + assertNotNull(result); + verify(cacheMock, never()).add(eq(IDENTIFIER), any(), any()); } @Test @@ -732,7 +734,7 @@ void loadEntityFromAxiomsResolvesConcreteClassForEntityTypeWithSubTypes() { new AxiomImpl<>(ID_RESOURCE, Assertion.createDataPropertyAssertion(URI.create(RDFS.LABEL), false), new Value<>("value")) ); - final OWLClassS result = mapper.loadEntity(new AxiomBasedLoadingParameters<>(OWLClassS.class, new EntityDescriptor(), false, axioms)); + final OWLClassS result = mapper.loadEntity(new AxiomBasedLoadingParameters<>(OWLClassS.class, axioms, new AxiomBasedLoadingConfigGroup<>(IDENTIFIER, new EntityDescriptor(), null))); assertInstanceOf(OWLClassR.class, result); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralObjectPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralObjectPropertyStrategyTest.java index 3fbd827dd..019048aea 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralObjectPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralObjectPropertyStrategyTest.java @@ -25,7 +25,10 @@ import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; import cz.cvut.kbss.jopa.model.IRI; +import cz.cvut.kbss.jopa.model.Subgraph; import cz.cvut.kbss.jopa.model.annotations.Id; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; @@ -34,9 +37,12 @@ import cz.cvut.kbss.jopa.model.metamodel.CollectionType; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.Identifier; +import cz.cvut.kbss.jopa.model.metamodel.Metamodel; import cz.cvut.kbss.jopa.oom.converter.ObjectOneOfEnumConverter; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingSetProxy; import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.vocabulary.OWL; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; @@ -99,11 +105,11 @@ void addValueFromAxiomLoadsInstanceAndAddsItIntoAttributeCollection() { final OWLClassJ instance = new OWLClassJ(ID); final URI aReference = Generators.createIndividualIdentifier(); final OWLClassA aInstance = new OWLClassA(aReference); - when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(Descriptor.class))) + when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(ObjectGraphInfo.class))) .thenReturn(aInstance); final Axiom axiom = new AxiomImpl<>(NamedResource.create(ID), - Assertion.createObjectPropertyAssertion( - URI.create(Vocabulary.P_HAS_A), false), new Value<>( + Assertion.createObjectPropertyAssertion( + URI.create(Vocabulary.P_HAS_A), false), new Value<>( NamedResource.create(aReference))); sut.addAxiomValue(axiom); @@ -111,12 +117,12 @@ void addValueFromAxiomLoadsInstanceAndAddsItIntoAttributeCollection() { assertNotNull(instance.getOwlClassA()); assertEquals(1, instance.getOwlClassA().size()); assertTrue(instance.getOwlClassA().contains(aInstance)); - verify(mapperMock).getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(Descriptor.class)); + verify(mapperMock).getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(ObjectGraphInfo.class)); } private PluralObjectPropertyStrategy strategy() { return new SimpleSetPropertyStrategy<>(mocks.forOwlClassJ().entityType(), mocks.forOwlClassJ().setAttribute(), - descriptor, mapperMock); + new ObjectGraphInfo(descriptor), mapperMock); } @Test @@ -124,18 +130,18 @@ void addValueFromAxiomDoesNothingWhenInstanceCannotBeLoaded() { final PluralObjectPropertyStrategy sut = strategy(); final OWLClassJ instance = new OWLClassJ(ID); final URI aReference = Generators.createIndividualIdentifier(); - when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(Descriptor.class))) + when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(ObjectGraphInfo.class))) .thenReturn(null); final Axiom axiom = new AxiomImpl<>(NamedResource.create(ID), - Assertion.createObjectPropertyAssertion( - URI.create(Vocabulary.P_HAS_A), false), new Value<>( + Assertion.createObjectPropertyAssertion( + URI.create(Vocabulary.P_HAS_A), false), new Value<>( NamedResource.create(aReference))); sut.addAxiomValue(axiom); sut.buildInstanceFieldValue(instance); assertNotNull(instance.getOwlClassA()); assertTrue(instance.getOwlClassA().isEmpty()); - verify(mapperMock).getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(Descriptor.class)); + verify(mapperMock).getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(ObjectGraphInfo.class)); } @Test @@ -144,21 +150,21 @@ void addValueFromAxiomGetsAttributeDescriptorFromEntityDescriptorForLoading() { descriptor.addAttributeDescriptor(mocks.forOwlClassJ().setAttribute(), aDescriptor); final PluralObjectPropertyStrategy sut = strategy(); final URI aReference = Generators.createIndividualIdentifier(); - when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(Descriptor.class))) + when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(ObjectGraphInfo.class))) .thenReturn(null); final Axiom axiom = new AxiomImpl<>(NamedResource.create(ID), - Assertion.createObjectPropertyAssertion( - URI.create(Vocabulary.P_HAS_A), false), new Value<>( + Assertion.createObjectPropertyAssertion( + URI.create(Vocabulary.P_HAS_A), false), new Value<>( NamedResource.create(aReference))); sut.addAxiomValue(axiom); - verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, aReference, aDescriptor); + verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, aReference, new ObjectGraphInfo(aDescriptor)); } @Test void buildAxiomValuesFromInstanceChecksForReferenceExistenceUsingTargetReferenceContext() { final Descriptor aDescriptor = new ObjectPropertyCollectionDescriptor(Generators.createIndividualIdentifier(), - mocks.forOwlClassJ().setAttribute()); + mocks.forOwlClassJ().setAttribute()); descriptor.addAttributeDescriptor(mocks.forOwlClassJ().setAttribute(), aDescriptor); final PluralObjectPropertyStrategy sut = strategy(); final ReferenceSavingResolver resolverMock = mock(ReferenceSavingResolver.class); @@ -173,11 +179,11 @@ void buildAxiomValuesFromInstanceChecksForReferenceExistenceUsingTargetReference @Test void buildAxiomValuesFromInstanceChecksForListItemReferenceExistenceUsingTargetReferenceContext() { final Descriptor aDescriptor = new ObjectPropertyCollectionDescriptor(Generators.createIndividualIdentifier(), - mocks.forOwlClassC().simpleListAtt()); + mocks.forOwlClassC().simpleListAtt()); descriptor.addAttributeDescriptor(mocks.forOwlClassC().simpleListAtt(), aDescriptor); final SimpleListPropertyStrategy sut = new SimpleListPropertyStrategy<>(mocks.forOwlClassC().entityType(), - mocks.forOwlClassC().simpleListAtt(), descriptor, mapperMock); + mocks.forOwlClassC().simpleListAtt(), new ObjectGraphInfo(descriptor), mapperMock); final ReferenceSavingResolver resolverMock = mock(ReferenceSavingResolver.class); sut.setReferenceSavingResolver(resolverMock); final OWLClassC instance = new OWLClassC(ID); @@ -193,7 +199,7 @@ void buildAxiomsFromInstanceReturnsAxiomsCorrespondingToAttributeValue() { instance.setSimpleList(Generators.generateInstances(5)); final SimpleListPropertyStrategy sut = new SimpleListPropertyStrategy<>(mocks.forOwlClassC().entityType(), - mocks.forOwlClassC().simpleListAtt(), descriptor, mapperMock); + mocks.forOwlClassC().simpleListAtt(), new ObjectGraphInfo(descriptor), mapperMock); when(mapperMock.getEntityType(OWLClassA.class)).thenReturn(mocks.forOwlClassA().entityType()); final Set> result = sut.buildAxiomsFromInstance(instance); @@ -204,14 +210,14 @@ void buildAxiomsFromInstanceReturnsAxiomsCorrespondingToAttributeValue() { .toURI(), false); instance.getSimpleList().forEach( a -> assertThat(result, hasItem(new AxiomImpl<>(subject, assertion, - new Value<>(NamedResource.create(a.getUri())))))); + new Value<>(NamedResource.create(a.getUri())))))); } @Test void buildAxiomsFromInstanceReturnsAxiomsWithIdentifiersWhenAttributeValueIsCollectionOfIdentifiers() { final SimpleSetPropertyStrategy sut = new SimpleSetPropertyStrategy<>(mocks.forOwlClassP().entityType(), - mocks.forOwlClassP().pUrlsAttribute(), descriptor, mapperMock); + mocks.forOwlClassP().pUrlsAttribute(), new ObjectGraphInfo(descriptor), mapperMock); final OWLClassP p = new OWLClassP(); p.setUri(Generators.createIndividualIdentifier()); p.setIndividualUrls(IntStream.range(0, 5).mapToObj(i -> { @@ -237,7 +243,7 @@ void buildAxiomsFromInstanceReturnsEmptyCollectionWhenAttributeValueIsEmpty() { instance.setSimpleList(null); final SimpleListPropertyStrategy sut = new SimpleListPropertyStrategy<>(mocks.forOwlClassC().entityType(), - mocks.forOwlClassC().simpleListAtt(), descriptor, mapperMock); + mocks.forOwlClassC().simpleListAtt(), new ObjectGraphInfo(descriptor), mapperMock); final Set> result = sut.buildAxiomsFromInstance(instance); assertNotNull(result); @@ -268,7 +274,7 @@ private SimpleSetPropertyStrategy initEnumAt final Identifier id = mock(Identifier.class); when(id.getJavaField()).thenReturn(EntityWithPluralObjectPropertyEnum.class.getDeclaredField("uri")); when(et.getIdentifier()).thenReturn(id); - return new SimpleSetPropertyStrategy<>(et, att, descriptor, mapperMock); + return new SimpleSetPropertyStrategy<>(et, att, new ObjectGraphInfo(descriptor), mapperMock); } static class EntityWithPluralObjectPropertyEnum { @@ -292,19 +298,21 @@ void buildAxiomsFromInstanceConvertsEnumConstantsToNamedResourcesForEnumValuedOb Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_m_objectOneOfEnumAttribute), false); assertThat(result, hasItems( new AxiomImpl<>(NamedResource.create(ID), assertion, - new Value<>(NamedResource.create(OWL.ANNOTATION_PROPERTY))), + new Value<>(NamedResource.create(OWL.ANNOTATION_PROPERTY))), new AxiomImpl<>(NamedResource.create(ID), assertion, - new Value<>(NamedResource.create(OWL.OBJECT_PROPERTY))) + new Value<>(NamedResource.create(OWL.OBJECT_PROPERTY))) )); } @Test void buildInstanceFieldValueSetsInstanceFieldValueToEmptyCollectionWhenNoAxiomsWereAdded() { // This ensures lazy loading proxy is replaced with empty collection when lazy loaded field loading is triggered - final SimpleSetPropertyStrategy sut = new SimpleSetPropertyStrategy<>(mocks.forOwlClassJ().entityType(), mocks.forOwlClassJ() - .setAttribute(), descriptor, mapperMock); + final SimpleSetPropertyStrategy sut = new SimpleSetPropertyStrategy<>(mocks.forOwlClassJ() + .entityType(), mocks.forOwlClassJ() + .setAttribute(), new ObjectGraphInfo(descriptor), mapperMock); final OWLClassJ instance = new OWLClassJ(ID); - instance.setOwlClassA(new LazyLoadingSetProxy<>(instance, mocks.forOwlClassJ().setAttribute(), mock(UnitOfWork.class))); + instance.setOwlClassA(new LazyLoadingSetProxy<>(instance, mocks.forOwlClassJ() + .setAttribute(), mock(UnitOfWork.class))); sut.buildInstanceFieldValue(instance); assertNotNull(instance.getOwlClassA()); assertTrue(instance.getOwlClassA().isEmpty()); @@ -312,8 +320,9 @@ void buildInstanceFieldValueSetsInstanceFieldValueToEmptyCollectionWhenNoAxiomsW @Test void lazilyAddAxiomValueSetsHasValueButDoesNotLoadReference() { - final SimpleSetPropertyStrategy sut = new SimpleSetPropertyStrategy<>(mocks.forOwlClassJ().entityType(), mocks.forOwlClassJ() - .setAttribute(), descriptor, mapperMock); + final SimpleSetPropertyStrategy sut = new SimpleSetPropertyStrategy<>(mocks.forOwlClassJ() + .entityType(), mocks.forOwlClassJ() + .setAttribute(), new ObjectGraphInfo(descriptor), mapperMock); final URI aReference = Generators.createIndividualIdentifier(); final Axiom axiom = new AxiomImpl<>(NamedResource.create(ID), Assertion.createObjectPropertyAssertion( @@ -326,19 +335,39 @@ void lazilyAddAxiomValueSetsHasValueButDoesNotLoadReference() { } @Test - void addValueFromAxiomUsesEntityDescriptorToGetInstanceFromCacheOrOntology() { + void addAxiomValueUsesEntityDescriptorToGetInstanceFromCacheOrOntology() { final PluralObjectPropertyStrategy sut = strategy(); final URI aReference = Generators.createIndividualIdentifier(); final OWLClassA aInstance = new OWLClassA(aReference); - when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(Descriptor.class))) + when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(ObjectGraphInfo.class))) .thenReturn(aInstance); final Axiom axiom = new AxiomImpl<>(NamedResource.create(ID), Assertion.createObjectPropertyAssertion( URI.create(Vocabulary.P_HAS_A), false), new Value<>(NamedResource.create(aReference))); sut.addAxiomValue(axiom); - final ArgumentCaptor captor = ArgumentCaptor.forClass(Descriptor.class); + final ArgumentCaptor captor = ArgumentCaptor.forClass(ObjectGraphInfo.class); verify(mapperMock).getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), captor.capture()); - assertInstanceOf(EntityDescriptor.class, captor.getValue()); + assertInstanceOf(EntityDescriptor.class, captor.getValue().descriptor()); + } + + @Test + void addAxiomValuePropagatesFetchGraphToReferencedEntityLoading() { + final Metamodel metamodel = mock(Metamodel.class); + mocks.setMocks(metamodel); + final EntityGraph fetchGraph = new EntityGraphImpl<>(mocks.forOwlClassJ().entityType(), metamodel); + final Subgraph subgraph = fetchGraph.addSubgraph("owlClassA"); + subgraph.addAttributeNodes("stringAttribute"); + final PluralObjectPropertyStrategy sut = new SimpleSetPropertyStrategy<>(mocks.forOwlClassJ() + .entityType(), mocks.forOwlClassJ() + .setAttribute(), + new ObjectGraphInfo(descriptor, new FetchGraphWrapper(fetchGraph)), mapperMock); + + sut.addAxiomValue(new AxiomImpl<>(NamedResource.create(ID), + Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false), + new Value<>(Generators.createIndividualIdentifier()))); + final ArgumentCaptor captor = ArgumentCaptor.forClass(ObjectGraphInfo.class); + verify(mapperMock).getEntityFromCacheOrOntology(eq(OWLClassA.class), any(), captor.capture()); + assertEquals(subgraph.getAttributeNodes(), captor.getValue().fetchGraph().getAttributeNodes()); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/RdfContainerObjectPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/RdfContainerObjectPropertyStrategyTest.java index 976b5892f..dfc511dc0 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/RdfContainerObjectPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/RdfContainerObjectPropertyStrategyTest.java @@ -30,6 +30,7 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.Identifier; import cz.cvut.kbss.jopa.model.metamodel.RdfContainerAttributeImpl; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.vocabulary.RDF; import cz.cvut.kbss.ontodriver.Connection; import cz.cvut.kbss.ontodriver.Containers; @@ -82,7 +83,7 @@ void addAxiomValueLoadsContainerContentAndAddsValuesFromIt() throws Exception { when(att.getIRI()).thenReturn(IRI.create(Vocabulary.ATTRIBUTE_BASE + "levels")); when(att.getCollectionType()).thenReturn(CollectionType.LIST); when(att.getBindableJavaType()).thenReturn(URI.class); - final RdfContainerObjectPropertyStrategy sut = new RdfContainerObjectPropertyStrategy<>(et, att, descriptor, mappingHelper); + final RdfContainerObjectPropertyStrategy sut = new RdfContainerObjectPropertyStrategy<>(et, att, new ObjectGraphInfo(descriptor), mappingHelper); sut.addAxiomValue(new AxiomImpl<>(NamedResource.create(ID), Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.ATTRIBUTE_BASE + "levels"), false), new Value<>(NamedResource.create(Generators.createIndividualIdentifier())))); final EntityWithContainer entity = new EntityWithContainer(); @@ -118,7 +119,7 @@ void buildAxiomValuesFromInstanceAddsContainerDescriptorWithListValuesToValueGat final Identifier idAtt = mock(Identifier.class); when(et.getIdentifier()).thenReturn(idAtt); when(idAtt.getJavaField()).thenReturn(EntityWithContainer.class.getDeclaredField("uri")); - final RdfContainerObjectPropertyStrategy sut = new RdfContainerObjectPropertyStrategy<>(et, att, descriptor, mappingHelper); + final RdfContainerObjectPropertyStrategy sut = new RdfContainerObjectPropertyStrategy<>(et, att, new ObjectGraphInfo(descriptor), mappingHelper); final EntityWithContainer entity = new EntityWithContainer(); entity.uri = ID; entity.levels = List.of(Generators.createIndividualIdentifier(), Generators.createIndividualIdentifier()); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ReferencedListPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ReferencedListPropertyStrategyTest.java index 35b2b3cf6..9c9beb8ea 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ReferencedListPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ReferencedListPropertyStrategyTest.java @@ -22,10 +22,10 @@ import cz.cvut.kbss.jopa.environment.OWLClassP; import cz.cvut.kbss.jopa.environment.OneOfEnum; import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.Identifier; import cz.cvut.kbss.jopa.model.metamodel.ListAttributeImpl; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.vocabulary.OWL; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListValueDescriptor; @@ -81,8 +81,8 @@ static void setUpBeforeClass() { public void setUp() throws Exception { super.setUp(); this.refListMock = mocks.forOwlClassC().referencedListAtt(); - this.strategy = new ReferencedListPropertyStrategy<>(mocks.forOwlClassC().entityType(), refListMock, descriptor, - mapperMock); + this.strategy = new ReferencedListPropertyStrategy<>(mocks.forOwlClassC() + .entityType(), refListMock, new ObjectGraphInfo(descriptor), mapperMock); strategy.setReferenceSavingResolver(new ReferenceSavingResolver(mapperMock)); } @@ -136,7 +136,7 @@ private List> initRefListAxioms(boolean includeNodes) throws Exception refListMock.getHasContentsPropertyIRI() .toURI(), refListMock.isInferred()), new Value<>(NamedResource.create(a.getUri()))); - when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(a.getUri()), any(Descriptor.class))).thenReturn(a); + when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(a.getUri()), any(ObjectGraphInfo.class))).thenReturn(a); axioms.add(content); previous = nodeUri; i++; @@ -164,7 +164,8 @@ void buildsInstanceFieldFromAxiomsWithoutNodes() throws Exception { void buildsInstanceFieldWithPlainIdentifiers() throws Exception { final ListAttributeImpl listAtt = mocks.forOwlClassP().pReferencedListAttribute(); final ReferencedListPropertyStrategy strategy = - new ReferencedListPropertyStrategy<>(mocks.forOwlClassP().entityType(), listAtt, descriptor, + new ReferencedListPropertyStrategy<>(mocks.forOwlClassP() + .entityType(), listAtt, new ObjectGraphInfo(descriptor), mapperMock); final List> axioms = initRefListAxioms(true); when(mapperMock.loadReferencedList(any(ReferencedListDescriptor.class))).thenReturn(axioms); @@ -182,7 +183,6 @@ void buildsInstanceFieldWithPlainIdentifiers() throws Exception { @Test void extractsValuesIntoAxiomsForSave() throws Exception { - when(mapperMock.isManagedType(OWLClassA.class)).thenReturn(true); final OWLClassC c = new OWLClassC(IDENTIFIER); c.setReferencedList(list); strategy.buildAxiomValuesFromInstance(c, builder); @@ -249,7 +249,8 @@ void extractsValuesIntoAxiomsForSaveFromNullList() throws Exception { void extractsValuesIntoAxiomsFromListOfPlainIdentifiers() throws Exception { final ListAttributeImpl listAtt = mocks.forOwlClassP().pReferencedListAttribute(); final ReferencedListPropertyStrategy strategy = - new ReferencedListPropertyStrategy<>(mocks.forOwlClassP().entityType(), listAtt, descriptor, + new ReferencedListPropertyStrategy<>(mocks.forOwlClassP() + .entityType(), listAtt, new ObjectGraphInfo(descriptor), mapperMock); final OWLClassP p = new OWLClassP(); p.setUri(IDENTIFIER); @@ -269,7 +270,8 @@ void extractValuesFromListSkipsNullItemsInListOfPlainIdentifiers() throws Except final List nonNulls = p.getReferencedList().stream().filter(Objects::nonNull).collect(Collectors.toList()); final ListAttributeImpl refList = mocks.forOwlClassP().pReferencedListAttribute(); final ReferencedListPropertyStrategy strategy = - new ReferencedListPropertyStrategy<>(mocks.forOwlClassP().entityType(), refList, descriptor, + new ReferencedListPropertyStrategy<>(mocks.forOwlClassP() + .entityType(), refList, new ObjectGraphInfo(descriptor), mapperMock); strategy.buildAxiomValuesFromInstance(p, builder); @@ -297,7 +299,7 @@ void extractListValuesConvertsEnumConstantsToNamedResourcesForEnumValuedObjectPr when(et.getIdentifier()).thenReturn(id); final ListAttributeImpl att = initEnumListAttribute(); final ReferencedListPropertyStrategy sut = - new ReferencedListPropertyStrategy<>(et, att, descriptor, mapperMock); + new ReferencedListPropertyStrategy<>(et, att, new ObjectGraphInfo(descriptor), mapperMock); final WithEnumList instance = new WithEnumList(); instance.uri = IDENTIFIER; instance.enumList = Arrays.asList(OneOfEnum.DATATYPE_PROPERTY, OneOfEnum.OBJECT_PROPERTY); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SimpleListPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SimpleListPropertyStrategyTest.java index 2171b902e..177358cc9 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SimpleListPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SimpleListPropertyStrategyTest.java @@ -24,11 +24,11 @@ import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; import cz.cvut.kbss.jopa.model.annotations.Sequence; -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.Identifier; import cz.cvut.kbss.jopa.model.metamodel.ListAttribute; import cz.cvut.kbss.jopa.model.metamodel.ListAttributeImpl; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.vocabulary.OWL; import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptor; import cz.cvut.kbss.ontodriver.descriptor.SimpleListValueDescriptor; @@ -78,7 +78,7 @@ public void setUp() throws Exception { super.setUp(); this.simpleList = mocks.forOwlClassC().simpleListAtt(); this.strategy = - new SimpleListPropertyStrategy<>(mocks.forOwlClassC().entityType(), simpleList, descriptor, mapperMock); + new SimpleListPropertyStrategy<>(mocks.forOwlClassC().entityType(), simpleList, new ObjectGraphInfo(descriptor), mapperMock); strategy.setReferenceSavingResolver(new ReferenceSavingResolver(mapperMock)); } @@ -111,7 +111,7 @@ private Collection> buildAxiomsForList(ListAttribute Assertion.createObjectPropertyAssertion(la.getHasNextPropertyIRI().toURI(), false), new Value<>(NamedResource.create(item.getUri()))); axioms.add(a); - when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(item.getUri()), any(Descriptor.class))) + when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(item.getUri()), any(ObjectGraphInfo.class))) .thenReturn(item); previous = item.getUri(); } @@ -122,7 +122,7 @@ private Collection> buildAxiomsForList(ListAttribute void buildsInstanceFieldOfPlainIdentifiersFromAxioms() { final ListAttributeImpl simpleList = mocks.forOwlClassP().pSimpleListAttribute(); final SimpleListPropertyStrategy strategy = - new SimpleListPropertyStrategy<>(mocks.forOwlClassP().entityType(), simpleList, descriptor, mapperMock); + new SimpleListPropertyStrategy<>(mocks.forOwlClassP().entityType(), simpleList, new ObjectGraphInfo(descriptor), mapperMock); final Axiom ax = new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createObjectPropertyAssertion(simpleList.getIRI() .toURI(), false), new Value<>(Generators.createIndividualIdentifier())); @@ -234,7 +234,7 @@ void extractValuesFromListOfPlainIdentifiersForPersist() throws Exception { p.setSimpleList(generateListOfIdentifiers()); final ListAttributeImpl simpleList = mocks.forOwlClassP().pSimpleListAttribute(); final SimpleListPropertyStrategy strategy = - new SimpleListPropertyStrategy<>(mocks.forOwlClassP().entityType(), simpleList, descriptor, mapperMock); + new SimpleListPropertyStrategy<>(mocks.forOwlClassP().entityType(), simpleList, new ObjectGraphInfo(descriptor), mapperMock); strategy.buildAxiomValuesFromInstance(p, builder); final SimpleListValueDescriptor valueDescriptor = listValueDescriptor(); @@ -250,7 +250,7 @@ void extractValuesFromListSkipsNullItemsInListOfPlainIdentifiers() throws Except final List nonNulls = p.getSimpleList().stream().filter(Objects::nonNull).collect(Collectors.toList()); final ListAttributeImpl simpleList = mocks.forOwlClassP().pSimpleListAttribute(); final SimpleListPropertyStrategy strategy = - new SimpleListPropertyStrategy<>(mocks.forOwlClassP().entityType(), simpleList, descriptor, mapperMock); + new SimpleListPropertyStrategy<>(mocks.forOwlClassP().entityType(), simpleList, new ObjectGraphInfo(descriptor), mapperMock); strategy.buildAxiomValuesFromInstance(p, builder); final SimpleListValueDescriptor valueDescriptor = listValueDescriptor(); @@ -277,7 +277,7 @@ void extractListValuesConvertsEnumConstantsToNamedResourcesForEnumValuedObjectPr when(et.getIdentifier()).thenReturn(id); final ListAttributeImpl att = initEnumListAttribute(); final SimpleListPropertyStrategy sut = - new SimpleListPropertyStrategy<>(et, att, descriptor, mapperMock); + new SimpleListPropertyStrategy<>(et, att, new ObjectGraphInfo(descriptor), mapperMock); final WithEnumList instance = new WithEnumList(); instance.uri = IDENTIFIER; instance.enumList = Arrays.asList(OneOfEnum.DATATYPE_PROPERTY, OneOfEnum.OBJECT_PROPERTY); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SimpleSetPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SimpleSetPropertyStrategyTest.java index d3c48079f..8434426ba 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SimpleSetPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SimpleSetPropertyStrategyTest.java @@ -33,6 +33,7 @@ import cz.cvut.kbss.jopa.model.metamodel.CollectionType; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.oom.converter.ObjectOneOfEnumConverter; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; import cz.cvut.kbss.jopa.vocabulary.OWL; import cz.cvut.kbss.ontodriver.descriptor.AxiomValueDescriptor; import cz.cvut.kbss.ontodriver.model.Assertion; @@ -113,7 +114,7 @@ void extractsValuesFromInstance() throws Exception { } private SimpleSetPropertyStrategy strategy(EntityType et, AbstractPluralAttribute, E> att) { - return new SimpleSetPropertyStrategy<>(et, att, descriptor, mapperMock); + return new SimpleSetPropertyStrategy<>(et, att, new ObjectGraphInfo(descriptor), mapperMock); } private void verifyExtractedValues(Set expected) throws Exception { diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularObjectPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularObjectPropertyStrategyTest.java index f48de39a1..7dc1f5e95 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularObjectPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularObjectPropertyStrategyTest.java @@ -21,18 +21,25 @@ import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.exceptions.CardinalityConstraintViolatedException; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; +import cz.cvut.kbss.jopa.model.Subgraph; import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.AbstractAttribute; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.model.metamodel.Metamodel; +import cz.cvut.kbss.jopa.oom.util.ObjectGraphInfo; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.vocabulary.OWL; import cz.cvut.kbss.ontodriver.descriptor.AxiomValueDescriptor; import cz.cvut.kbss.ontodriver.model.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; @@ -88,7 +95,7 @@ void buildInstanceFieldSupportsPlainIdentifierValues() throws Exception { private FieldStrategy, T> strategy(EntityType et, AbstractAttribute att) { - return new SingularObjectPropertyStrategy<>(et, att, descriptor, mapperMock); + return new SingularObjectPropertyStrategy<>(et, att, new ObjectGraphInfo(descriptor), mapperMock); } private Assertion propertyP() throws Exception { @@ -246,7 +253,7 @@ void addValueGetsAttributeDescriptorFromEntityDescriptorForLoading() { final Assertion assertion = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false); sut.addAxiomValue( new AxiomImpl<>(NamedResource.create(IDENTIFIER), assertion, new Value<>(NamedResource.create(VALUE)))); - verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, VALUE, aDescriptor); + verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, VALUE, new ObjectGraphInfo(aDescriptor)); } @Test @@ -362,4 +369,23 @@ void addValueSupportsUriAxiomValue() throws Exception { strategy.buildInstanceFieldValue(p); assertEquals(VALUE, p.getIndividualUri()); } + + @Test + void addAxiomValuePropagatesFetchGraphToReferencedEntityLoading() { + final Metamodel metamodel = mock(Metamodel.class); + metamodelMocks.setMocks(metamodel); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassD() + .entityType(), metamodel); + final Subgraph sg = fetchGraph.addSubgraph("owlClassA"); + sg.addAttributeNodes("stringAttribute"); + final FieldStrategy, OWLClassD> sut = new SingularObjectPropertyStrategy<>(metamodelMocks.forOwlClassD() + .entityType(), + metamodelMocks.forOwlClassD() + .owlClassAAtt(), new ObjectGraphInfo(descriptor, new FetchGraphWrapper(fetchGraph)), mapperMock); + sut.setReferenceSavingResolver(referenceResolverMock); + sut.addAxiomValue(new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false), new Value<>(VALUE))); + final ArgumentCaptor captor = ArgumentCaptor.forClass(ObjectGraphInfo.class); + verify(mapperMock).getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(VALUE), captor.capture()); + assertEquals(sg.getAttributeNodes(), captor.getValue().fetchGraph().getAttributeNodes()); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TwoStepInstanceLoaderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TwoStepInstanceLoaderTest.java index c52e55c59..1d5cd64fb 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TwoStepInstanceLoaderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TwoStepInstanceLoaderTest.java @@ -23,6 +23,7 @@ import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.ontodriver.Types; @@ -107,7 +108,8 @@ void loadEntityLoadsEntityFromStorageWhenEntityTypeIsDetermined() throws Excepti final Collection> axioms = new HashSet<>(types); when(connectionMock.find(axiomDescriptor)).thenReturn(axioms); when(entityConstructorMock - .reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, metamodelMock.entity(OWLClassR.class), descriptor, false), + .reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, metamodelMock.entity(OWLClassR.class), descriptor, new FetchGraphWrapper(), + false), axioms)) .thenReturn(entityR); @@ -126,7 +128,7 @@ void loadEntityResolvesTypeFromProvidedAxiomsAndReconstructsEntity() { new Value<>(new LangString("Test", "en")))); final OWLClassR entityR = new OWLClassR(); when(entityConstructorMock - .reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, metamodelMock.entity(OWLClassR.class), descriptor, false), + .reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, metamodelMock.entity(OWLClassR.class), descriptor, new FetchGraphWrapper(), false), axioms)) .thenReturn(entityR); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoaderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoaderTest.java index 0621bcee1..9096b7ec1 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoaderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoaderTest.java @@ -18,15 +18,27 @@ package cz.cvut.kbss.jopa.query.sparql.loader; import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.QueryResultLoader; +import cz.cvut.kbss.jopa.model.Subgraph; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.iteration.ResultRow; +import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; +import cz.cvut.kbss.ontodriver.model.AxiomImpl; +import cz.cvut.kbss.ontodriver.model.NamedResource; import cz.cvut.kbss.ontodriver.model.Value; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,18 +55,22 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.emptyCollectionOf; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -69,60 +85,71 @@ class AttributeBasedRowsToAxiomsQueryResultLoaderTest { @Mock private MetamodelImpl metamodel; + private MetamodelMocks metamodelMocks; + private final Descriptor descriptor = new EntityDescriptor(); @BeforeEach void setUp() throws Exception { - MetamodelMocks metamodelMocks = new MetamodelMocks(); + this.metamodelMocks = new MetamodelMocks(); metamodelMocks.setMocks(metamodel); when(uow.getMetamodel()).thenReturn(metamodel); } @Test void loadEntityInstanceReadsRowIntoAxiomsAndLoadsEntityFromThem() throws OntoDriverException { + final EntityGraph fetchGraph = createFetchGraph(); final AttributeBasedRowsToAxiomsQueryResultLoader sut = - new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassA.class, descriptor); + new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassA.class, descriptor, fetchGraph); final OWLClassA instance = Generators.generateOwlClassAInstance(); instance.setTypes(Set.of()); final List resultRows = Stream.concat(mockResultRows(instance).stream(), mockResultRows( Generators.generateOwlClassAInstance()).stream()) .toList(); - when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor))).thenReturn(instance); + mockOwlClassAProjectedVariables(sut); + when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class))).thenReturn(instance); final Optional result = resultRows.stream().map(sut::loadResult).filter(Optional::isPresent) .map(Optional::get).findFirst(); assertTrue(result.isPresent()); assertEquals(instance, result.get()); - verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor)); + verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(new AxiomBasedLoadingConfigGroup<>(instance.getUri(), descriptor, fetchGraph))); + } + + private EntityGraph createFetchGraph() { + final IdentifiableEntityType et = metamodel.entity(OWLClassA.class); + final EntityGraph fetchGraph = new EntityGraphImpl<>(et, metamodel); + fetchGraph.addAttributeNodes(et.getAttributes().toArray(Attribute[]::new)); + return fetchGraph; } private static List mockResultRows(OWLClassA instance) throws OntoDriverException { final List allTypes = Stream.concat(Stream.of(OWLClassA.getClassIri()), instance.getTypes().stream()) .toList(); - final List rows = new ArrayList<>(allTypes.size()); - for (String type : allTypes) { - final ResultRow row = mock(ResultRow.class); - when(row.getColumnCount()).thenReturn(3); - when(row.getColumnNames()).thenReturn(List.of("x", "xstringAttribute", "xtypes")); - when(row.getObject(0, URI.class)).thenReturn(instance.getUri()); - if (instance.getStringAttribute() != null) { - when(row.isBound("xstringAttribute")).thenReturn(true); - when(row.getObject("xstringAttribute")).thenReturn(instance.getStringAttribute()); - } - when(row.isBound("xtypes")).thenReturn(true); - when(row.getObject("xtypes")).thenReturn(URI.create(type)); - rows.add(row); + final ResultRow row = mock(ResultRow.class); + when(row.getColumnCount()).thenReturn(3); + when(row.getColumnNames()).thenReturn(List.of("x", "x_stringAttribute", "x_types")); + when(row.getObject(0, URI.class)).thenReturn(instance.getUri()); + when(row.isBound("x")).thenReturn(true); + when(row.getObject("x", URI.class)).thenReturn(instance.getUri()); + if (instance.getStringAttribute() != null) { + when(row.isBound("x_stringAttribute")).thenReturn(true); + when(row.getObject("x_stringAttribute")).thenReturn(instance.getStringAttribute()); } - return rows; + when(row.isBound("x_types")).thenReturn(true); + when(row.getString("x_types")).thenReturn(String.join(AttributeEnumeratingSparqlAssemblyModifier.GROUP_CONCAT_SEPARATOR, allTypes)); + return List.of(row); } @Test void loadLastPendingLoadsLastPendingEntity() throws Exception { + final EntityGraph fetchGraph = createFetchGraph(); final AttributeBasedRowsToAxiomsQueryResultLoader sut = - new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassA.class, descriptor); + new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassA.class, descriptor, fetchGraph); final OWLClassA instance = Generators.generateOwlClassAInstance(); instance.setTypes(Set.of()); final List resultRows = mockResultRows(instance); - when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor))).thenReturn(instance); + mockOwlClassAProjectedVariables(sut); + when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class))).thenReturn(instance); resultRows.forEach(row -> { final Optional opt = sut.loadResult(row); assertFalse(opt.isPresent()); @@ -130,17 +157,28 @@ void loadLastPendingLoadsLastPendingEntity() throws Exception { final Optional result = sut.loadLastPending(); assertTrue(result.isPresent()); assertEquals(instance, result.get()); - verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor)); + verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(new AxiomBasedLoadingConfigGroup<>(instance.getUri(), descriptor, fetchGraph))); + } + + private void mockOwlClassAProjectedVariables(QueryResultLoader loader) { + final AttributeEnumeratingSparqlAssemblyModifier modifier = mock(AttributeEnumeratingSparqlAssemblyModifier.class); + doReturn(List.of( + new QueryVariableMapping("x", "x_stringAttribute", metamodelMocks.forOwlClassA().stringAttribute()), + new QueryVariableMapping("x", "x_types", metamodelMocks.forOwlClassA().typesSpec()) + )).when(modifier).getVariableMapping(); + loader.visit(modifier); } @Test void loadResultDoesNotDuplicateAlreadyPresentAttributes() throws Exception { + final EntityGraph fetchGraph = createFetchGraph(); final AttributeBasedRowsToAxiomsQueryResultLoader sut = - new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassA.class, descriptor); + new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassA.class, descriptor, fetchGraph); final OWLClassA instance = Generators.generateOwlClassAInstance(); instance.setTypes(Set.of()); final List resultRows = mockResultRows(instance); - when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor))).thenReturn(instance); + mockOwlClassAProjectedVariables(sut); + when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class))).thenReturn(instance); resultRows.forEach(row -> { final Optional opt = sut.loadResult(row); assertFalse(opt.isPresent()); @@ -148,7 +186,7 @@ void loadResultDoesNotDuplicateAlreadyPresentAttributes() throws Exception { final Optional result = sut.loadLastPending(); assertTrue(result.isPresent()); final ArgumentCaptor>> captor = ArgumentCaptor.forClass(Collection.class); - verify(uow).readObjectFromAxioms(eq(OWLClassA.class), captor.capture(), eq(descriptor)); + verify(uow).readObjectFromAxioms(eq(OWLClassA.class), captor.capture(), eq(new AxiomBasedLoadingConfigGroup<>(instance.getUri(), descriptor, fetchGraph))); assertEquals(1, captor.getValue().stream() .filter(ax -> ax.getValue().equals(new Value<>(instance.getStringAttribute()))).count()); } @@ -156,12 +194,13 @@ void loadResultDoesNotDuplicateAlreadyPresentAttributes() throws Exception { @Test void loadResultHandlesUnboundAttributeVariables() throws Exception { final AttributeBasedRowsToAxiomsQueryResultLoader sut = - new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassA.class, descriptor); + new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassA.class, descriptor, createFetchGraph()); final OWLClassA instance = Generators.generateOwlClassAInstance(); instance.setTypes(Set.of()); instance.setStringAttribute(null); final List resultRows = mockResultRows(instance); - when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor))).thenReturn(instance); + mockOwlClassAProjectedVariables(sut); + when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class))).thenReturn(instance); resultRows.forEach(row -> { final Optional opt = sut.loadResult(row); assertFalse(opt.isPresent()); @@ -171,4 +210,110 @@ void loadResultHandlesUnboundAttributeVariables() throws Exception { assertNull(result.get().getStringAttribute()); assertThat(result.get().getTypes(), anyOf(nullValue(), emptyCollectionOf(String.class))); } + + @Test + void loadResultSupportsFetchGraphWithMultipleEntities() throws Exception { + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodel.entity(OWLClassD.class), metamodel); + final Subgraph sg = fetchGraph.addSubgraph("owlClassA"); + sg.addAttributeNodes("stringAttribute"); + final AttributeBasedRowsToAxiomsQueryResultLoader sut = new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassD.class, descriptor, fetchGraph); + final OWLClassD instance = new OWLClassD(Generators.createIndividualIdentifier()); + instance.setOwlClassA(Generators.generateOwlClassAInstance()); + final List resultRows = mockResultRows(instance); + mockOwlClassDProjectedVariable(sut); + resultRows.forEach(row -> { + final Optional opt = sut.loadResult(row); + assertFalse(opt.isPresent()); + }); + sut.loadLastPending(); + final ArgumentCaptor>> captor = ArgumentCaptor.forClass(Collection.class); + verify(uow).readObjectFromAxioms(eq(OWLClassD.class), captor.capture(), eq(new AxiomBasedLoadingConfigGroup<>(instance.getUri(), descriptor, fetchGraph))); + assertThat(captor.getValue(), hasItem( + new AxiomImpl<>(NamedResource.create(instance.getUri()), Assertion.createClassAssertion(false), new Value<>(URI.create(Vocabulary.c_OwlClassD))))); + assertThat(captor.getValue(), hasItem(new AxiomImpl<>(NamedResource.create(instance.getUri()), Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_h_hasA), false), new Value<>(instance.getOwlClassA() + .getUri())))); + assertThat(captor.getValue(), hasItem(new AxiomImpl<>(NamedResource.create(instance.getOwlClassA() + .getUri()), Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_a_stringAttribute), false), new Value<>(instance.getOwlClassA() + .getStringAttribute())))); + assertThat(captor.getValue(), hasItem(new AxiomImpl<>(NamedResource.create(instance.getOwlClassA() + .getUri()), Assertion.createClassAssertion(false), new Value<>(URI.create(Vocabulary.c_OwlClassA))))); + + instance.getOwlClassA().getTypes() + .forEach(t -> assertThat(captor.getValue(), hasItem(new AxiomImpl<>(NamedResource.create(instance.getOwlClassA() + .getUri()), Assertion.createClassAssertion(false), new Value<>(URI.create(t)))))); + } + + private void mockOwlClassDProjectedVariable(QueryResultLoader loader) { + final AttributeEnumeratingSparqlAssemblyModifier modifier = mock(AttributeEnumeratingSparqlAssemblyModifier.class); + doReturn(List.of( + new QueryVariableMapping("x", "x_types", null), + new QueryVariableMapping("x", "x_owlClassA", metamodelMocks.forOwlClassD().owlClassAAtt()), + new QueryVariableMapping("x_owlClassA", "x_owlClassA_stringAttribute", metamodelMocks.forOwlClassA() + .stringAttribute()), + new QueryVariableMapping("x_owlClassA", "x_owlClassA_types", metamodelMocks.forOwlClassA().typesSpec()) + )).when(modifier).getVariableMapping(); + loader.visit(modifier); + } + + private static List mockResultRows(OWLClassD instance) throws Exception { + final List aTypes = Stream.concat(Stream.of(OWLClassA.getClassIri()), instance.getOwlClassA().getTypes() + .stream()).toList(); + final List rows = new ArrayList<>(aTypes.size()); + for (String type : aTypes) { + final ResultRow row = mock(ResultRow.class); + when(row.getColumnCount()).thenReturn(5); + when(row.getColumnNames()).thenReturn(List.of("x", "x_types", "x_owlClassA", "x_owlClassA_stringAttribute", "x_owlClassA_types")); + when(row.getObject(0, URI.class)).thenReturn(instance.getUri()); + when(row.isBound("x")).thenReturn(true); + when(row.getObject("x", URI.class)).thenReturn(instance.getUri()); + when(row.isBound("x_types")).thenReturn(true); + when(row.getObject("x_types")).thenReturn(URI.create(Vocabulary.c_OwlClassD)); + when(row.isBound("x_owlClassA")).thenReturn(true); + when(row.getObject("x_owlClassA")).thenReturn(instance.getOwlClassA().getUri()); + when(row.getObject("x_owlClassA", URI.class)).thenReturn(instance.getOwlClassA().getUri()); + when(row.isBound("x_owlClassA_stringAttribute")).thenReturn(true); + when(row.getObject("x_owlClassA_stringAttribute")).thenReturn(instance.getOwlClassA().getStringAttribute()); + when(row.isBound("x_owlClassA_types")).thenReturn(true); + when(row.getString("x_owlClassA_types")).thenReturn(type); + rows.add(row); + } + return rows; + } + + @Test + void loadResultSupportsGroupConcatenatedTypes() throws Exception { + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassA() + .entityType(), metamodel); + fetchGraph.addAttributeNodes("stringAttribute"); + final AttributeBasedRowsToAxiomsQueryResultLoader sut = new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassA.class, descriptor, fetchGraph); + final OWLClassA instance = Generators.generateOwlClassAInstance(); + final ResultRow row = mock(ResultRow.class); + when(row.getColumnCount()).thenReturn(3); + when(row.getColumnNames()).thenReturn(List.of("x", "x_stringAttribute", "x_types")); + when(row.getObject(0, URI.class)).thenReturn(instance.getUri()); + when(row.isBound("x")).thenReturn(true); + when(row.getObject("x", URI.class)).thenReturn(instance.getUri()); + when(row.isBound("x_stringAttribute")).thenReturn(true); + when(row.getObject("x_stringAttribute")).thenReturn(instance.getStringAttribute()); + when(row.isBound("x_types")).thenReturn(true); + final Stream aTypes = Stream.concat(Stream.of(Vocabulary.c_OwlClassA), instance.getTypes().stream()); + when(row.getString("x_types")).thenReturn(aTypes.collect(Collectors.joining(AttributeEnumeratingSparqlAssemblyModifier.GROUP_CONCAT_SEPARATOR))); + when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class))).thenReturn(instance); + + final Optional intermediate = sut.loadResult(row); + assertFalse(intermediate.isPresent()); + final Optional result = sut.loadLastPending(); + assertTrue(result.isPresent()); + final ArgumentCaptor>> captor = ArgumentCaptor.forClass(Collection.class); + verify(uow).readObjectFromAxioms(eq(OWLClassA.class), captor.capture(), any(AxiomBasedLoadingConfigGroup.class)); + captor.getValue().forEach(ax -> assertEquals(instance.getUri(), ax.getSubject().getIdentifier())); + instance.getTypes().forEach(type -> assertTrue(captor.getValue().stream() + .anyMatch(ax -> ax.getValue().stringValue() + .equals(type)))); + assertTrue(captor.getValue().stream() + .anyMatch(ax -> ax.getValue().stringValue().equals(Vocabulary.c_OwlClassA))); + assertTrue(captor.getValue().stream() + .anyMatch(ax -> ax.getValue().stringValue().equals(instance.getStringAttribute()))); + } + } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeEnumeratingSparqlAssemblyModifierTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeEnumeratingSparqlAssemblyModifierTest.java index 9fc9aa330..11e6bd2d1 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeEnumeratingSparqlAssemblyModifierTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeEnumeratingSparqlAssemblyModifierTest.java @@ -17,34 +17,46 @@ */ package cz.cvut.kbss.jopa.query.sparql.loader; +import cz.cvut.kbss.jopa.environment.OWLClassJ; import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; import cz.cvut.kbss.jopa.query.parameter.ParameterValueFactory; import cz.cvut.kbss.jopa.query.sparql.Sparql11QueryParser; import cz.cvut.kbss.jopa.query.sparql.TokenStreamSparqlQueryHolder; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; import cz.cvut.kbss.jopa.sessions.MetamodelProvider; import cz.cvut.kbss.jopa.utils.IdentifierTransformer; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import java.net.URI; +import static cz.cvut.kbss.jopa.query.sparql.loader.AttributeEnumeratingSparqlAssemblyModifier.GROUP_CONCAT_SEPARATOR; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalToCompressingWhiteSpace; +import static org.hamcrest.Matchers.not; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) class AttributeEnumeratingSparqlAssemblyModifierTest { private static MetamodelMocks metamodelMocks; @@ -52,6 +64,9 @@ class AttributeEnumeratingSparqlAssemblyModifierTest { @Mock private ConnectionWrapper connectionWrapper; + @Mock + private MetamodelImpl metamodel; + private final ParameterValueFactory valueFactory = new ParameterValueFactory(mock(MetamodelProvider.class)); private final Sparql11QueryParser parser = new Sparql11QueryParser(valueFactory); @@ -64,18 +79,22 @@ static void setUpBeforeAll() throws Exception { @BeforeEach void setUp() { when(connectionWrapper.getRepositoryMetadata()).thenReturn(() -> "test"); + metamodelMocks.setMocks(metamodel); } @Test - void modifyAlwaysAddsTriplePatternForEntityTypes() { + void modifyAlwaysAddsTriplePatternForEntityType() { final TokenStreamSparqlQueryHolder holder = parser.parseQuery("SELECT ?x WHERE { ?x a ?type . }"); final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassD().entityType()); holder.setAssemblyModifier(sut); final String result = holder.assembleQuery(); - assertThat(result, equalToCompressingWhiteSpace("SELECT ?x ?xowlClassA ?xtypes WHERE { ?x a ?type . " + - "OPTIONAL { ?x " + strUri(Vocabulary.p_h_hasA) + " ?xowlClassA . } " + - "?x a ?xtypes . }")); + assertThat(result, projectsVariable("x")); + assertThat(result, containsString("?x a " + strUri(Vocabulary.c_OwlClassD) + " .")); + } + + private static ProjectsVariableMatcher projectsVariable(String variable) { + return new ProjectsVariableMatcher(variable); } private static String strUri(String uri) { @@ -83,12 +102,12 @@ private static String strUri(String uri) { } private AttributeEnumeratingSparqlAssemblyModifier createSut(IdentifiableEntityType et) { - return createSut(et, new EntityDescriptor()); + return createSut(et, new EntityDescriptor(), null); } private AttributeEnumeratingSparqlAssemblyModifier createSut(IdentifiableEntityType et, - Descriptor descriptor) { - return new AttributeEnumeratingSparqlAssemblyModifier(et, descriptor, connectionWrapper); + Descriptor descriptor, EntityGraph fetchGraph) { + return new AttributeEnumeratingSparqlAssemblyModifier(metamodel, et, descriptor, fetchGraph, connectionWrapper); } @Test @@ -98,9 +117,9 @@ void modifyAddsOptionalTriplePatternForEntityAttributesAndProjectsAttributeValue holder.setAssemblyModifier(sut); final String result = holder.assembleQuery(); - assertThat(result, equalToCompressingWhiteSpace("SELECT ?x ?xstringAttribute ?xtypes WHERE { ?x a ?type . " + - "OPTIONAL { ?x " + strUri(Vocabulary.p_a_stringAttribute) + " ?xstringAttribute . } " + - "?x a ?xtypes . }")); + assertThat(result, projectsVariable("x")); + assertThat(result, projectsVariable("x_stringAttribute")); + assertThat(result, containsString("OPTIONAL { ?x " + strUri(Vocabulary.p_a_stringAttribute) + " ?x_stringAttribute . }")); } @Test @@ -109,13 +128,12 @@ void modifyAddsOptionalTriplePatternWithGraphWhenDescriptorWithNonDefaultContext final URI ctx = Generators.createIndividualIdentifier(); final EntityDescriptor descriptor = new EntityDescriptor(ctx); final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassA() - .entityType(), descriptor); + .entityType(), descriptor, null); holder.setAssemblyModifier(sut); final String result = holder.assembleQuery(); - assertThat(result, equalToCompressingWhiteSpace("SELECT ?x ?xstringAttribute ?xtypes WHERE { ?x a ?type . " + - "OPTIONAL { GRAPH <" + ctx + "> { ?x " + strUri(Vocabulary.p_a_stringAttribute) + " ?xstringAttribute . } } " + - "GRAPH <" + ctx + "> { ?x a ?xtypes . } }")); + assertThat(result, containsString("GRAPH <" + ctx + "> { ?x a ?x_types . }")); + assertThat(result, containsString("OPTIONAL { GRAPH <" + ctx + "> { ?x " + strUri(Vocabulary.p_a_stringAttribute) + " ?x_stringAttribute . } }")); } @Test @@ -125,36 +143,56 @@ void modifyAddsOptionalTriplePatternWithAttributeSpecifiedContextResolvedFromDes final EntityDescriptor descriptor = new EntityDescriptor(); descriptor.addAttributeContext(metamodelMocks.forOwlClassA().entityType().getAttribute("stringAttribute"), ctx); final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassA() - .entityType(), descriptor); + .entityType(), descriptor, null); holder.setAssemblyModifier(sut); final String result = holder.assembleQuery(); - assertThat(result, equalToCompressingWhiteSpace("SELECT ?x ?xstringAttribute ?xtypes WHERE { ?x a ?type . " + - "OPTIONAL { GRAPH <" + ctx + "> { ?x " + strUri(Vocabulary.p_a_stringAttribute) + " ?xstringAttribute . } } " + - "?x a ?xtypes . }")); + assertThat(result, containsString("?x a ?x_types .")); + assertThat(result, containsString("OPTIONAL { GRAPH <" + ctx + "> { ?x " + strUri(Vocabulary.p_a_stringAttribute) + " ?x_stringAttribute . } }")); } @Test void modifyAddsOptionalTriplePatternsForSubtypeAttributes() { - final TokenStreamSparqlQueryHolder holder = parser.parseQuery("SELECT ?x WHERE { ?x a ?type . }"); - final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassS().entityType()); - holder.setAssemblyModifier(sut); + when(metamodelMocks.forOwlClassR().rOwlClassAAtt().getFetchType()).thenReturn(FetchType.EAGER); + try { + final TokenStreamSparqlQueryHolder holder = parser.parseQuery("SELECT ?x WHERE { ?x a ?type . }"); + final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassS() + .entityType()); + holder.setAssemblyModifier(sut); - final String result = holder.assembleQuery(); - assertThat(result, containsString(Vocabulary.P_R_STRING_ATTRIBUTE)); - assertThat(result, containsString(Vocabulary.P_HAS_A)); + final String result = holder.assembleQuery(); + assertThat(result, containsString(Vocabulary.P_R_STRING_ATTRIBUTE)); + assertThat(result, containsString(Vocabulary.P_HAS_A)); + } finally { + when(metamodelMocks.forOwlClassR().rOwlClassAAtt().getFetchType()).thenReturn(FetchType.LAZY); + } } @Test void modifyAddsTriplePatternWithoutOptionalWhenMinimumParticipationConstraintIsPresentAndGreaterThanZero() { + when(metamodelMocks.forOwlClassJ().setAttribute().getFetchType()).thenReturn(FetchType.EAGER); + try { + final TokenStreamSparqlQueryHolder holder = parser.parseQuery("SELECT ?x WHERE { ?x a ?type . }"); + final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassJ() + .entityType()); + holder.setAssemblyModifier(sut); + + final String result = holder.assembleQuery(); + assertThat(result, containsString("?x " + strUri(Vocabulary.p_h_hasA) + " ?x_owlClassA . ")); + } finally { + when(metamodelMocks.forOwlClassJ().setAttribute().getFetchType()).thenReturn(FetchType.LAZY); + } + } + + @Test + void modifyExcludesLazilyLoadedAttributesFromFetching() { final TokenStreamSparqlQueryHolder holder = parser.parseQuery("SELECT ?x WHERE { ?x a ?type . }"); final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassJ().entityType()); holder.setAssemblyModifier(sut); final String result = holder.assembleQuery(); - assertThat(result, equalToCompressingWhiteSpace("SELECT ?x ?xowlClassA ?xtypes WHERE { ?x a ?type . " + - "?x " + strUri(Vocabulary.p_h_hasA) + " ?xowlClassA . " + - "?x a ?xtypes . }")); + assertThat(result, not(projectsVariable("x_owlClassA"))); + assertThat(result, not(containsString("?x " + strUri(Vocabulary.p_h_hasA) + " ?x_owlClassA . "))); } @Test @@ -164,10 +202,84 @@ void modifyUsesDefaultContextForInferredAttributeWhenRepositoryProductIsGraphDBA final EntityDescriptor descriptor = new EntityDescriptor(ctx); when(connectionWrapper.getRepositoryMetadata()).thenReturn(() -> "GraphDB"); final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassF() - .entityType(), descriptor); + .entityType(), descriptor, null); holder.setAssemblyModifier(sut); final String result = holder.assembleQuery(); - assertThat(result, containsString("OPTIONAL { ?x " + strUri(Vocabulary.p_f_stringAttribute) + " ?xsecondStringAttribute . }")); + assertThat(result, containsString("OPTIONAL { ?x " + strUri(Vocabulary.p_f_stringAttribute) + " ?x_secondStringAttribute . }")); + } + + @Test + void modifyUsesGroupConcatForPluralAssociationAttributesToReduceRowCountBlowup() { + // Just for this test + when(metamodelMocks.forOwlClassA().entityType().getTypes()).thenReturn(null); + try { + final TokenStreamSparqlQueryHolder holder = parser.parseQuery("SELECT ?x WHERE { ?x a ?type . }"); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassJ() + .entityType(), metamodel); + fetchGraph.addAttributeNodes("owlClassA"); + final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassJ() + .entityType(), new EntityDescriptor(), fetchGraph); + holder.setAssemblyModifier(sut); + + final String result = holder.assembleQuery(); + assertThat(result, containsString("GROUP_CONCAT(DISTINCT ?x_owlClassA; SEPARATOR='" + GROUP_CONCAT_SEPARATOR + "') AS ?x_owlClassA_gc")); + assertThat(result, containsString("GROUP BY ?x")); + } finally { + // Reset + when(metamodelMocks.forOwlClassA().entityType() + .getTypes()).thenReturn((TypesSpecification) metamodelMocks.forOwlClassA().typesSpec()); + } + } + + @Test + void modifyUsesGroupConcatForTypesToReduceRowCountBlowup() { + final TokenStreamSparqlQueryHolder holder = parser.parseQuery("SELECT ?x WHERE { ?x a ?type . }"); + final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassA().entityType()); + holder.setAssemblyModifier(sut); + + final String result = holder.assembleQuery(); + assertThat(result, containsString("GROUP_CONCAT(DISTINCT ?x_types; SEPARATOR='" + GROUP_CONCAT_SEPARATOR + "') AS ?x_types_gc")); + assertThat(result, containsString("GROUP BY ?x ?x_stringAttribute")); + } + + @Test + void modifyGroupConcatenatesOnlyMostSpecificEligibleAttributeToPreserveGrouping() { + final TokenStreamSparqlQueryHolder holder = parser.parseQuery("SELECT ?x WHERE { ?x a ?type . }"); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassJ() + .entityType(), metamodel); + fetchGraph.addSubgraph("owlClassA"); + final AttributeEnumeratingSparqlAssemblyModifier sut = createSut(metamodelMocks.forOwlClassJ() + .entityType(), new EntityDescriptor(), fetchGraph); + holder.setAssemblyModifier(sut); + + final String result = holder.assembleQuery(); + assertThat(result, not(containsString("GROUP_CONCAT(DISTINCT ?x_owlClassA; SEPARATOR='" + GROUP_CONCAT_SEPARATOR + "') AS ?x_owlClassA_gc"))); + assertThat(result, containsString("GROUP_CONCAT(DISTINCT ?x_owlClassA_types; SEPARATOR='" + GROUP_CONCAT_SEPARATOR + "') AS ?x_owlClassA_types_gc")); + assertThat(result, containsString("GROUP BY ?x ?x_owlClassA")); + } + + /** + * Simplified Hamcrest matcher that checks if a SPARQL query projects a specific variable. The matcher extracts the + * projection part of the query (everything before the WHERE clause) and checks if it contains the specified + * variable prefixed with '?'. + */ + private static class ProjectsVariableMatcher extends TypeSafeMatcher { + private final String variable; + + public ProjectsVariableMatcher(String variable) { + this.variable = variable; + } + + @Override + protected boolean matchesSafely(String query) { + final String projectionPart = query.substring(0, query.indexOf("WHERE")); + return projectionPart.contains("?" + variable); + } + + @Override + public void describeTo(Description description) { + description.appendText("a SPARQL query that projects variable ?" + variable); + } } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/EntityMappingQueryModifierTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/EntityMappingQueryModifierTest.java new file mode 100644 index 000000000..47cf2a4df --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/EntityMappingQueryModifierTest.java @@ -0,0 +1,107 @@ +package cz.cvut.kbss.jopa.query.sparql.loader; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.OWLClassE; +import cz.cvut.kbss.jopa.environment.Vocabulary; +import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.Subgraph; +import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import java.util.List; +import java.util.Optional; + +import static cz.cvut.kbss.jopa.utils.IdentifierTransformer.stringifyIri; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItems; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class EntityMappingQueryModifierTest { + + private MetamodelMocks metamodelMocks; + + @Mock + private MetamodelImpl metamodel; + + @BeforeEach + void setUp() throws Exception { + this.metamodelMocks = new MetamodelMocks(); + metamodelMocks.setMocks(metamodel); + } + + @Test + void modifyGeneratesBasicGraphPatternForSpecifiedFetchGraph() { + final EntityMappingQueryModifier sut = new EntityMappingQueryModifier(metamodel, metamodelMocks.forOwlClassD() + .entityType(), + new EntityDescriptor(), false); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassD() + .entityType(), metamodel); + fetchGraph.addAttributeNodes("owlClassA"); + final EntityMappingQueryModifier.QueryModification result = sut.modify(fetchGraph, "x"); + assertEquals(List.of("x_owlClassA", "x_types"), result.variableNames()); + assertThat(result.queryPart(), containsString("OPTIONAL { ?x " + stringifyIri(Vocabulary.P_HAS_A) + " ?x_owlClassA . }")); + assertThat(result.queryPart(), containsString("?x a " + stringifyIri(Vocabulary.c_OwlClassD) + " .")); + } + + @Test + void modifyGeneratesSubGraphPatternForSpecifiedFetchGraph() { + final EntityMappingQueryModifier sut = new EntityMappingQueryModifier(metamodel, metamodelMocks.forOwlClassD() + .entityType(), + new EntityDescriptor(), false); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassD() + .entityType(), metamodel); + final Subgraph sg = fetchGraph.addSubgraph("owlClassA"); + sg.addAttributeNodes("stringAttribute"); + final EntityMappingQueryModifier.QueryModification result = sut.modify(fetchGraph, "x"); + assertThat(result.variableNames(), hasItems("x_owlClassA", "x_owlClassA_stringAttribute", "x_owlClassA_types")); + assertThat(result.queryPart(), containsString("?x a " + stringifyIri(Vocabulary.c_OwlClassD) + " .")); + assertThat(result.queryPart(), containsString("OPTIONAL { ?x " + stringifyIri(Vocabulary.P_HAS_A) + " ?x_owlClassA . " + + "?x_owlClassA a ?x_owlClassA_types . " + + "OPTIONAL { ?x_owlClassA " + stringifyIri(Vocabulary.p_a_stringAttribute) + " ?x_owlClassA_stringAttribute . } }")); + } + + @Test + void modifyGeneratesExactTypeTriplePatternWhenEntityClassDoesNotHaveTypesField() { + final EntityMappingQueryModifier sut = new EntityMappingQueryModifier(metamodel, metamodelMocks.forOwlClassE() + .entityType(), + new EntityDescriptor(), false); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassE() + .entityType(), metamodel); + fetchGraph.addAttributeNodes("stringAttribute"); + final EntityMappingQueryModifier.QueryModification result = sut.modify(fetchGraph, "x"); + assertThat(result.queryPart(), containsString("?x a " + stringifyIri(Vocabulary.c_OwlClassE) + " .")); + } + + @Test + void modifyBindsEntityTypeAndGeneratesQueryVariableMappingForItWhenEntityClassHasNoTypesField() { + final EntityMappingQueryModifier sut = new EntityMappingQueryModifier(metamodel, metamodelMocks.forOwlClassE() + .entityType(), + new EntityDescriptor(), false); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassE() + .entityType(), metamodel); + fetchGraph.addAttributeNodes("stringAttribute"); + final EntityMappingQueryModifier.QueryModification result = sut.modify(fetchGraph, "x"); + assertEquals(List.of("x_stringAttribute", "x_types"), result.variableNames()); + final Optional typesMapping = result.variables().stream() + .filter(QueryVariableMapping::isTypes) + .findFirst(); + assertTrue(typesMapping.isPresent()); + assertNull(typesMapping.get().attribute()); + assertThat(result.queryPart(), containsString("BIND (" + stringifyIri(Vocabulary.c_OwlClassE) + " AS ?x_types)")); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/FetchGraphProcessorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/FetchGraphProcessorTest.java new file mode 100644 index 000000000..4729e4388 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/FetchGraphProcessorTest.java @@ -0,0 +1,73 @@ +package cz.cvut.kbss.jopa.query.sparql.loader; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.Subgraph; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItems; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class FetchGraphProcessorTest { + + @Mock + private MetamodelImpl metamodel; + + private static MetamodelMocks metamodelMocks; + + @BeforeAll + static void setUpBeforeAll() throws Exception { + metamodelMocks = new MetamodelMocks(); + } + + @BeforeEach + void setUp() { + metamodelMocks.setMocks(metamodel); + } + + @Test + void mapFetchGraphToProjectionCreatesMappingsForAttributesOfSingleEntityClass() { + final FetchGraphProcessor sut = new FetchGraphProcessor(metamodel); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassA() + .entityType(), metamodel); + fetchGraph.addAttributeNodes(metamodelMocks.forOwlClassA().stringAttribute()); + final List result = sut.mapFetchGraphToProjection(fetchGraph, metamodelMocks.forOwlClassA() + .entityType(), "x"); + assertThat(result, hasItems(new QueryVariableMapping("x", "x_stringAttribute", metamodelMocks.forOwlClassA() + .stringAttribute()), + new QueryVariableMapping("x", "x_types", metamodelMocks.forOwlClassA().typesSpec()))); + } + + @Test + void mapFetchGraphToProjectionCreatesMappingsForAttributesOfTwoLinkedEntities() { + final FetchGraphProcessor sut = new FetchGraphProcessor(metamodel); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassD() + .entityType(), metamodel); + final Subgraph sg = fetchGraph.addSubgraph(metamodelMocks.forOwlClassD().owlClassAAtt()); + sg.addAttributeNodes(metamodelMocks.forOwlClassA().stringAttribute()); + final List result = sut.mapFetchGraphToProjection(fetchGraph, metamodelMocks.forOwlClassD() + .entityType(), "x"); + assertThat(result, hasItems( + new QueryVariableMapping("x", "x_owlClassA", metamodelMocks.forOwlClassD() + .owlClassAAtt()), + new QueryVariableMapping("x_owlClassA", "x_owlClassA_stringAttribute", metamodelMocks.forOwlClassA() + .stringAttribute()), + new QueryVariableMapping("x_owlClassA", "x_owlClassA_types", metamodelMocks.forOwlClassA() + .typesSpec()))); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizerTest.java index 321165852..51e65ee89 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizerTest.java @@ -19,8 +19,11 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.OWLClassB; +import cz.cvut.kbss.jopa.environment.OWLClassS; import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.QueryResultLoader; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; @@ -45,7 +48,6 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -67,7 +69,7 @@ void setUp() { } @Test - void optimizeQueryAssemblySetsUnboundPredicateObjectAssemblyModifierWhenQueryIsSelectAndResultClassIsEntityTypeWithProperties() { + void modifyQueryAssemblySetsUnboundPredicateObjectAssemblyModifierWhenQueryIsSelectAndResultClassIsEntityTypeWithProperties() { final TokenStreamSparqlQueryHolder qh = spy(parser.parseQuery("SELECT ?s WHERE { ?s a ?type }")); final SparqlQueryResultLoadingOptimizer sut = new SparqlQueryResultLoadingOptimizer(qh, uow, connectionWrapper); sut.enableOptimization(); @@ -77,12 +79,12 @@ void optimizeQueryAssemblySetsUnboundPredicateObjectAssemblyModifierWhenQueryIsS final IdentifiableEntityType et = mock(IdentifiableEntityType.class); when(et.getProperties()).thenReturn(mock(PropertiesSpecification.class)); when(metamodel.entity(OWLClassB.class)).thenReturn(et); - sut.optimizeQueryAssembly(OWLClassB.class, new EntityDescriptor()); + sut.modifyQueryAssembly(OWLClassB.class, new EntityDescriptor(), null); verify(qh).setAssemblyModifier(any(UnboundPredicateObjectSparqlAssemblyModifier.class)); } @Test - void optimizeQueryAssemblySetsAttributeEnumeratingAssemblyModifierWhenQueryIsSelectAndResultClassIsEntityTypeWithoutProperties() { + void modifyQueryAssemblySetsAttributeEnumeratingAssemblyModifierWhenQueryIsSelectAndResultClassIsEntityTypeWithoutProperties() { final TokenStreamSparqlQueryHolder qh = spy(parser.parseQuery("SELECT ?s WHERE { ?s a ?type }")); when(connectionWrapper.getRepositoryMetadata()).thenReturn(mock(RepositoryMetadata.class)); final SparqlQueryResultLoadingOptimizer sut = new SparqlQueryResultLoadingOptimizer(qh, uow, connectionWrapper); @@ -93,41 +95,41 @@ void optimizeQueryAssemblySetsAttributeEnumeratingAssemblyModifierWhenQueryIsSel final IdentifiableEntityType et = mock(IdentifiableEntityType.class); when(et.getProperties()).thenReturn(null); when(metamodel.entity(OWLClassA.class)).thenReturn(et); - sut.optimizeQueryAssembly(OWLClassA.class, new EntityDescriptor()); + sut.modifyQueryAssembly(OWLClassA.class, new EntityDescriptor(), null); verify(qh).setAssemblyModifier(any(AttributeEnumeratingSparqlAssemblyModifier.class)); } @Test - void optimizeQueryAssemblyDoesNotOptimizeQueryWhenResultClassIsNotEntity() { + void optimizeQueryAssemblyDoesNotModifyQueryWhenResultClassIsNotEntity() { final TokenStreamSparqlQueryHolder qh = spy(parser.parseQuery("SELECT ?s WHERE { ?s a ?type }")); final SparqlQueryResultLoadingOptimizer sut = new SparqlQueryResultLoadingOptimizer(qh, uow, connectionWrapper); sut.enableOptimization(); - sut.optimizeQueryAssembly(URI.class, new EntityDescriptor()); - verify(qh, never()).setAssemblyModifier(any()); + sut.modifyQueryAssembly(URI.class, new EntityDescriptor(), null); + verify(qh).setAssemblyModifier(any(NoopSparqlAssemblyModifier.class)); } @Test - void optimizeQueryAssemblyDoesNotOptimizeQueryWhenLimitIsSet() { + void optimizeQueryAssemblyDoesNotModifyQueryWhenLimitIsSet() { final TokenStreamSparqlQueryHolder qh = spy(parser.parseQuery("SELECT ?s WHERE { ?s a ?type } LIMIT 1")); final SparqlQueryResultLoadingOptimizer sut = new SparqlQueryResultLoadingOptimizer(qh, uow, connectionWrapper); sut.enableOptimization(); when(uow.isEntityType(OWLClassA.class)).thenReturn(true); - sut.optimizeQueryAssembly(OWLClassA.class, new EntityDescriptor()); - verify(qh, never()).setAssemblyModifier(any()); + sut.modifyQueryAssembly(OWLClassA.class, new EntityDescriptor(), null); + verify(qh).setAssemblyModifier(any(NoopSparqlAssemblyModifier.class)); } @Test - void optimizeQueryAssemblyDoesNotOptimizeQueryWhenOffsetIsSet() { + void optimizeQueryAssemblyDoesNotModifyQueryWhenOffsetIsSet() { final TokenStreamSparqlQueryHolder qh = spy(parser.parseQuery("SELECT ?s WHERE { ?s a ?type } OFFSET 1")); final SparqlQueryResultLoadingOptimizer sut = new SparqlQueryResultLoadingOptimizer(qh, uow, connectionWrapper); sut.enableOptimization(); when(uow.isEntityType(OWLClassA.class)).thenReturn(true); - sut.optimizeQueryAssembly(OWLClassA.class, new EntityDescriptor()); - verify(qh, never()).setAssemblyModifier(any()); + sut.modifyQueryAssembly(OWLClassA.class, new EntityDescriptor(), null); + verify(qh).setAssemblyModifier(any(NoopSparqlAssemblyModifier.class)); } @Test - void optimizeQueryAssemblyOptimizesQueryWhenItContainsGraphClauseAndAttributeBasedOptimizerCanBeUser() { + void modifyQueryAssemblyOptimizesQueryWhenItContainsGraphClauseAndAttributeBasedOptimizerCanBeUser() { final TokenStreamSparqlQueryHolder qh = spy(parser.parseQuery("SELECT ?s WHERE { GRAPH ?g { ?s a ?type } }")); when(connectionWrapper.getRepositoryMetadata()).thenReturn(mock(RepositoryMetadata.class)); final SparqlQueryResultLoadingOptimizer sut = new SparqlQueryResultLoadingOptimizer(qh, uow, connectionWrapper); @@ -137,18 +139,18 @@ void optimizeQueryAssemblyOptimizesQueryWhenItContainsGraphClauseAndAttributeBas when(uow.getMetamodel()).thenReturn(metamodel); final IdentifiableEntityType et = mock(IdentifiableEntityType.class); when(metamodel.entity(OWLClassA.class)).thenReturn(et); - sut.optimizeQueryAssembly(OWLClassA.class, new EntityDescriptor()); + sut.modifyQueryAssembly(OWLClassA.class, new EntityDescriptor(), null); verify(qh).setAssemblyModifier(any(AttributeEnumeratingSparqlAssemblyModifier.class)); } @Test - void optimizeQueryAssemblyDoesNotOptimizeQueryWhenProvidedDescriptorSpecifiesMoreThanOneContext() { + void optimizeQueryAssemblyDoesNotModifyQueryWhenProvidedDescriptorSpecifiesMoreThanOneContext() { final TokenStreamSparqlQueryHolder qh = spy(parser.parseQuery("SELECT ?s WHERE { ?s a ?type }")); final SparqlQueryResultLoadingOptimizer sut = new SparqlQueryResultLoadingOptimizer(qh, uow, connectionWrapper); sut.enableOptimization(); when(uow.isEntityType(OWLClassA.class)).thenReturn(true); - sut.optimizeQueryAssembly(OWLClassA.class, new EntityDescriptor(Set.of(Generators.createIndividualIdentifier(), Generators.createIndividualIdentifier()))); - verify(qh, never()).setAssemblyModifier(any()); + sut.modifyQueryAssembly(OWLClassA.class, new EntityDescriptor(Set.of(Generators.createIndividualIdentifier(), Generators.createIndividualIdentifier())), null); + verify(qh).setAssemblyModifier(any(NoopSparqlAssemblyModifier.class)); } @Test @@ -161,8 +163,40 @@ void getQueryResultLoaderReturnsAttributeBasedRowsToAxiomsResultLoaderWhenQueryP when(uow.getMetamodel()).thenReturn(metamodel); final SparqlQueryResultLoadingOptimizer sut = new SparqlQueryResultLoadingOptimizer(qh, uow, connectionWrapper); sut.disableOptimization(); + sut.modifyQueryAssembly(OWLClassA.class, new EntityDescriptor(), null); when(uow.isEntityType(OWLClassA.class)).thenReturn(true); - final QueryResultLoader result = sut.getQueryResultLoader(OWLClassA.class, new EntityDescriptor()); + final QueryResultLoader result = sut.getQueryResultLoader(OWLClassA.class, new EntityDescriptor(), null); assertInstanceOf(AttributeBasedRowsToAxiomsQueryResultLoader.class, result); } + + @Test + void modifyQueryAssemblyUsesAttributeEnumeratingSparqlAssemblyModifierForEntityWithSubTypesWhenFetchGraphIsProvided() { + when(connectionWrapper.getRepositoryMetadata()).thenReturn(() -> ""); + final TokenStreamSparqlQueryHolder qh = spy(parser.parseQuery("SELECT ?s WHERE { ?s a ?type }")); + final SparqlQueryResultLoadingOptimizer sut = new SparqlQueryResultLoadingOptimizer(qh, uow, connectionWrapper); + sut.enableOptimization(); + when(uow.isEntityType(OWLClassS.class)).thenReturn(true); + final MetamodelImpl metamodel = mock(MetamodelImpl.class); + when(uow.getMetamodel()).thenReturn(metamodel); + final IdentifiableEntityType et = mock(IdentifiableEntityType.class); + when(metamodel.entity(OWLClassS.class)).thenReturn(et); + final EntityGraph fetchGraph = new EntityGraphImpl<>(et, metamodel); + sut.modifyQueryAssembly(OWLClassS.class, new EntityDescriptor(), fetchGraph); + verify(qh).setAssemblyModifier(any(AttributeEnumeratingSparqlAssemblyModifier.class)); + } + + @Test + void modifyQueryAssemblyUsesAttributeEnumeratingSparqlAssemblyModifierForEntityWithPropertiesWhenFetchGraphIsProvided() { + when(connectionWrapper.getRepositoryMetadata()).thenReturn(() -> ""); + final TokenStreamSparqlQueryHolder qh = spy(parser.parseQuery("SELECT ?s WHERE { ?s a ?type }")); + final SparqlQueryResultLoadingOptimizer sut = new SparqlQueryResultLoadingOptimizer(qh, uow, connectionWrapper); + sut.enableOptimization(); + when(uow.isEntityType(OWLClassB.class)).thenReturn(true); + final MetamodelImpl metamodel = mock(MetamodelImpl.class); + when(uow.getMetamodel()).thenReturn(metamodel); + final IdentifiableEntityType et = mock(IdentifiableEntityType.class); + when(metamodel.entity(OWLClassB.class)).thenReturn(et); + sut.modifyQueryAssembly(OWLClassB.class, new EntityDescriptor(), null); + verify(qh).setAssemblyModifier(any(AttributeEnumeratingSparqlAssemblyModifier.class)); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/TripleBasedRowsToAxiomsQueryResultLoaderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/TripleBasedRowsToAxiomsQueryResultLoaderTest.java index 1518fee4e..29529c94b 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/TripleBasedRowsToAxiomsQueryResultLoaderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/TripleBasedRowsToAxiomsQueryResultLoaderTest.java @@ -26,6 +26,7 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; import cz.cvut.kbss.jopa.vocabulary.RDF; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.iteration.ResultRow; @@ -45,6 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -83,13 +85,13 @@ void loadEntityInstanceReadsInitialRowsIntoAxiomsAndLoadsEntityFromThem() throws when(lastRow.getObject(1, URI.class)).thenReturn(URI.create(RDF.TYPE)); when(lastRow.getObject(2)).thenReturn(URI.create(Vocabulary.c_OwlClassA)); final List rows = Stream.concat(firstResultRows.stream(), Stream.of(lastRow)).toList(); - when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor))).thenReturn(instance); + when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class))).thenReturn(instance); final Optional result = rows.stream().map(sut::loadResult).filter(Optional::isPresent) .map(Optional::get).findFirst(); assertTrue(result.isPresent()); assertEquals(instance, result.get()); - verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor)); + verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(new AxiomBasedLoadingConfigGroup<>(instance.getUri(), descriptor))); } private static List mockResultRows(OWLClassA instance) throws OntoDriverException { @@ -122,13 +124,13 @@ void loadResultLoadsMultipleInstancesFromConsecutiveRows() throws OntoDriverExce final List rows = Stream.concat(firstResultRows.stream(), secondResultRows.stream()) .collect(Collectors.toList()); rows.add(lastRow); - when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor))).thenReturn(instanceOne) - .thenReturn(instanceTwo); + when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class))).thenReturn(instanceOne) + .thenReturn(instanceTwo); final List result = rows.stream().map(sut::loadResult).filter(Optional::isPresent) .map(Optional::get).toList(); assertEquals(List.of(instanceOne, instanceTwo), result); - verify(uow, times(2)).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor)); + verify(uow, times(2)).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class)); } @Test @@ -137,13 +139,13 @@ void loadLastPendingLoadsEntityInstanceFromPendingAxioms() throws OntoDriverExce sut = new TripleBasedRowsToAxiomsQueryResultLoader<>(uow, OWLClassA.class, descriptor); final OWLClassA instance = Generators.generateOwlClassAInstance(); final List firstResultRows = mockResultRows(instance); - when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor))).thenReturn(instance); + when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class))).thenReturn(instance); assertTrue(firstResultRows.stream().map(sut::loadResult).noneMatch(Optional::isPresent)); final Optional result = sut.loadLastPending(); assertTrue(result.isPresent()); assertEquals(instance, result.get()); - verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor)); + verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(new AxiomBasedLoadingConfigGroup<>(instance.getUri(), descriptor))); } @Test @@ -158,13 +160,13 @@ void loadEntityInstanceLoadsEntityUsingNormalUoWLoadWhenLoadFromAxiomsThrowsCard when(lastRow.getObject(1, URI.class)).thenReturn(URI.create(RDF.TYPE)); when(lastRow.getObject(2)).thenReturn(URI.create(Vocabulary.c_OwlClassA)); final List rows = Stream.concat(firstResultRows.stream(), Stream.of(lastRow)).toList(); - when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor))).thenThrow(CardinalityConstraintViolatedException.class); + when(uow.readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class))).thenThrow(CardinalityConstraintViolatedException.class); when(uow.readObject(OWLClassA.class, instance.getUri(), descriptor)).thenReturn(instance); final Optional result = rows.stream().map(sut::loadResult).filter(Optional::isPresent) .map(Optional::get).findFirst(); assertTrue(result.isPresent()); - verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(descriptor)); + verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(new AxiomBasedLoadingConfigGroup<>(instance.getUri(), descriptor))); verify(uow).readObject(OWLClassA.class, instance.getUri(), descriptor); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java index 591556e47..31b9ba99f 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java @@ -33,6 +33,7 @@ import cz.cvut.kbss.jopa.proxy.lazy.gen.LazyLoadingEntityProxyGenerator; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingParameters; import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; @@ -562,14 +563,14 @@ void readObjectFromAxiomsLoadsEntityFromSpecifiedAxiomsAndReturnsIt() { new AxiomImpl<>(idResource, Assertion.createClassAssertion(false), new Value<>(NamedResource.create(OWLClassA.getClassIri()))), new AxiomImpl<>(idResource, Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_a_stringAttribute), false), new Value<>(entityA.getStringAttribute())) ); - when(storageMock.loadFromAxioms(new AxiomBasedLoadingParameters<>(OWLClassA.class, descriptor, false, axioms))).thenReturn(entityA); + when(storageMock.loadFromAxioms(new AxiomBasedLoadingParameters<>(OWLClassA.class, axioms, new AxiomBasedLoadingConfigGroup<>(id, descriptor)))).thenReturn(entityA); - final OWLClassA result = uow.readObjectFromAxioms(OWLClassA.class, axioms, descriptor); + final OWLClassA result = uow.readObjectFromAxioms(OWLClassA.class, axioms, new AxiomBasedLoadingConfigGroup<>(id, descriptor)); assertNotNull(result); assertEquals(entityA.getUri(), result.getUri()); assertEquals(entityA.getStringAttribute(), result.getStringAttribute()); assertTrue(uow.contains(result)); - verify(storageMock).loadFromAxioms(new AxiomBasedLoadingParameters<>(OWLClassA.class, descriptor, false, axioms)); + verify(storageMock).loadFromAxioms(new AxiomBasedLoadingParameters<>(OWLClassA.class, axioms, new AxiomBasedLoadingConfigGroup<>(id, descriptor))); assertNotSame(entityA, result); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ReadOnlyUnitOfWorkTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ReadOnlyUnitOfWorkTest.java index 41860fb3c..d8ca377ec 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ReadOnlyUnitOfWorkTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ReadOnlyUnitOfWorkTest.java @@ -37,6 +37,7 @@ import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; +import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingParameters; import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; @@ -730,14 +731,14 @@ void readObjectFromAxiomsLoadsEntityFromSpecifiedAxiomsAndReturnsIt() { new AxiomImpl<>(idResource, Assertion.createClassAssertion(false), new Value<>(NamedResource.create(OWLClassA.getClassIri()))), new AxiomImpl<>(idResource, Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_a_stringAttribute), false), new Value<>(entityA.getStringAttribute())) ); - when(storageMock.loadFromAxioms(new AxiomBasedLoadingParameters<>(OWLClassA.class, descriptor, true, axioms))).thenReturn(entityA); + when(storageMock.loadFromAxioms(new AxiomBasedLoadingParameters<>(OWLClassA.class, axioms, new AxiomBasedLoadingConfigGroup<>(id, descriptor, null)))).thenReturn(entityA); - final OWLClassA result = uow.readObjectFromAxioms(OWLClassA.class, axioms, descriptor); + final OWLClassA result = uow.readObjectFromAxioms(OWLClassA.class, axioms, new AxiomBasedLoadingConfigGroup<>(id, descriptor, null)); assertNotNull(result); assertEquals(entityA.getUri(), result.getUri()); assertEquals(entityA.getStringAttribute(), result.getStringAttribute()); assertTrue(uow.contains(result)); - verify(storageMock).loadFromAxioms(new AxiomBasedLoadingParameters<>(OWLClassA.class, descriptor, true, axioms)); + verify(storageMock).loadFromAxioms(new AxiomBasedLoadingParameters<>(OWLClassA.class, axioms, new AxiomBasedLoadingConfigGroup<>(id, descriptor, null))); assertSame(entityA, result); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactoryTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactoryTest.java index 6dcd4df15..1fe40d029 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactoryTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactoryTest.java @@ -19,11 +19,16 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.OWLClassC; +import cz.cvut.kbss.jopa.environment.OWLClassL; import cz.cvut.kbss.jopa.environment.OWLClassM; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.EntityGraphImpl; import cz.cvut.kbss.jopa.model.LoadState; +import cz.cvut.kbss.jopa.model.metamodel.Metamodel; import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingListProxy; +import cz.cvut.kbss.jopa.sessions.util.FetchGraphWrapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -121,4 +126,43 @@ void createCreatesDescriptorWithNotLoadedForLazyLoadedAttributesContainingProxyI assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassC().referencedListAtt())); assertEquals(LoadState.NOT_LOADED, result.isLoaded(metamodelMocks.forOwlClassC().simpleListAtt())); } + + @Test + void createForFetchGraphCreatesLoadStateDescriptorWithAttributesInFetchGraphLoadedAndOtherDataAttributesAsUnknown() { + final OWLClassM instance = new OWLClassM(); + instance.setKey(Generators.createIndividualIdentifier().toString()); + final Metamodel metamodel = mock(Metamodel.class); + metamodelMocks.setMocks(metamodel); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassM() + .entityType(), metamodel); + fetchGraph.addAttributeNodes("intAttribute", "longAttribute", "doubleAttribute", "characterAttribute"); + final LoadStateDescriptor result = LoadStateDescriptorFactory.createForFetchGraph(instance, metamodelMocks.forOwlClassM() + .entityType(), new FetchGraphWrapper(fetchGraph)); + assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassM().identifier())); + assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassM().integerAttribute())); + assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassM().longAttribute())); + assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassM().doubleAttribute())); + assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassM().characterAttribute())); + assertEquals(LoadState.UNKNOWN, result.isLoaded(metamodelMocks.forOwlClassM().dateAttribute())); + assertEquals(LoadState.UNKNOWN, result.isLoaded(metamodelMocks.forOwlClassM().integerSetAttribute())); + assertEquals(LoadState.UNKNOWN, result.isLoaded(metamodelMocks.forOwlClassM().booleanAttribute())); + assertEquals(LoadState.UNKNOWN, result.isLoaded(metamodelMocks.forOwlClassM().lexicalFormAttribute())); + } + + @Test + void createForFetchGraphCreatesLoadStateDescriptorWithObjectPropertyAttributesNotInFetchGraphAsNotLoaded() { + final OWLClassL instance = new OWLClassL(Generators.createIndividualIdentifier()); + final Metamodel metamodel = mock(Metamodel.class); + metamodelMocks.setMocks(metamodel); + final EntityGraph fetchGraph = new EntityGraphImpl<>(metamodelMocks.forOwlClassL() + .entityType(), metamodel); + fetchGraph.addAttributeNodes("simpleList", "singleA"); + final LoadStateDescriptor result = LoadStateDescriptorFactory.createForFetchGraph(instance, metamodelMocks.forOwlClassL() + .entityType(), new FetchGraphWrapper(fetchGraph)); + assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassL().identifier())); + assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassL().simpleListAtt())); + assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassL().owlClassAAtt())); + assertEquals(LoadState.NOT_LOADED, result.isLoaded(metamodelMocks.forOwlClassL().referencedListAtt())); + assertEquals(LoadState.NOT_LOADED, result.isLoaded(metamodelMocks.forOwlClassL().setAttribute())); + } } diff --git a/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/query/owlapi/TypedQueryTest.java b/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/query/owlapi/TypedQueryTest.java index a4ae28d6a..56f12d4ae 100644 --- a/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/query/owlapi/TypedQueryTest.java +++ b/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/query/owlapi/TypedQueryTest.java @@ -123,4 +123,11 @@ public void querySupportsOptimizedEntityLoading() { public void optimizedEntityLoadingWorksForEntityClassesWithoutTypesField() { // OWL2Query does not support OPTIONAL } + + @Disabled + @Test + @Override + public void querySupportsFetchGraphs() { + // OWL2Query does not support OPTIONAL + } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassDD.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassDD.java deleted file mode 100644 index cc973a420..000000000 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassDD.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * JOPA - * Copyright (C) 2026 Czech Technical University in Prague - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package cz.cvut.kbss.jopa.test; - -import cz.cvut.kbss.jopa.model.annotations.*; - -import java.net.URI; - -@SparqlResultSetMapping(name = OWLClassD.MAPPING_NAME, entities = { - @EntityResult(entityClass = OWLClassD.class, fields = { - @FieldResult(name = "uri", variable = "x"), - @FieldResult(name = "owlClassA", variable = "y") - }) -}) -@OWLClass(iri = Vocabulary.C_OWL_CLASS_D) -public class OWLClassDD implements HasUri { - - public static final String MAPPING_NAME = "OWLClassD.entityMapping"; - - @Id - private URI uri; - - @OWLObjectProperty(iri = Vocabulary.P_HAS_OWL_CLASS_A, fetch = FetchType.LAZY) - // @ParticipationConstraints({ - // @ParticipationConstraint(owlObjectIRI="http://new.owl#OWLClassA", min=1, - // max=1) - // }) - private OWLClassA owlClassA; - - public OWLClassDD() { - // Default public no-arg constructor - } - - public OWLClassDD(URI uri) { - this.uri = uri; - } - - /** - * @param uri the uri to set - */ - public void setUri(URI uri) { - this.uri = uri; - } - - /** - * @return the uri - */ - @Override - public URI getUri() { - return uri; - } - - public void setOwlClassA(OWLClassA owlClassA) { - this.owlClassA = owlClassA; - } - - public OWLClassA getOwlClassA() { - return owlClassA; - } - - @Override - public String toString() { - String out = "OWLClassD: uri = " + uri; - out += ", owlClassA = " + owlClassA; - return out; - } -} diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassFF.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassFF.java deleted file mode 100644 index c549942e2..000000000 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassFF.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * JOPA - * Copyright (C) 2026 Czech Technical University in Prague - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package cz.cvut.kbss.jopa.test; - -import cz.cvut.kbss.jopa.model.annotations.FetchType; -import cz.cvut.kbss.jopa.model.annotations.Id; -import cz.cvut.kbss.jopa.model.annotations.Inferred; -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 java.net.URI; -import java.util.Set; - -@OWLClass(iri = Vocabulary.C_OWL_CLASS_F) -public class OWLClassFF implements HasUri { - - @Id - private URI uri; - - @Inferred - @OWLDataProperty(iri = Vocabulary.P_F_STRING_ATTRIBUTE) - private String secondStringAttribute; - - @OWLObjectProperty(iri = Vocabulary.P_F_HAS_SIMPLE_SET, fetch = FetchType.EAGER) - private Set simpleSet; - - public OWLClassFF() { - } - - public OWLClassFF(URI uri) { - this.uri = uri; - } - - @Override - public URI getUri() { - return uri; - } - - public void setUri(URI uri) { - this.uri = uri; - } - - public String getSecondStringAttribute() { - return secondStringAttribute; - } - - public void setSecondStringAttribute(String secondStringAttribute) { - this.secondStringAttribute = secondStringAttribute; - } - - public Set getSimpleSet() { - return simpleSet; - } - - public void setSimpleSet(Set simpleSet) { - this.simpleSet = simpleSet; - } - - @Override - public String toString() { - String out = "OWLClassFF: uri = " + uri; - out += ", secondStringAttribute = " + secondStringAttribute; - return out; - } -} diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/QueryTestEnvironment.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/QueryTestEnvironment.java index 79192dfa6..2de794b22 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/QueryTestEnvironment.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/QueryTestEnvironment.java @@ -20,15 +20,34 @@ import cz.cvut.kbss.jopa.model.EntityManager; import cz.cvut.kbss.jopa.model.MultilingualString; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.test.*; +import cz.cvut.kbss.jopa.test.OWLClassA; +import cz.cvut.kbss.jopa.test.OWLClassB; +import cz.cvut.kbss.jopa.test.OWLClassC; +import cz.cvut.kbss.jopa.test.OWLClassD; +import cz.cvut.kbss.jopa.test.OWLClassE; +import cz.cvut.kbss.jopa.test.OWLClassJ; +import cz.cvut.kbss.jopa.test.OWLClassM; +import cz.cvut.kbss.jopa.test.OWLClassT; +import cz.cvut.kbss.jopa.test.OWLClassV; +import cz.cvut.kbss.jopa.test.OWLClassY; +import cz.cvut.kbss.jopa.test.Thing; +import cz.cvut.kbss.jopa.test.Vocabulary; import cz.cvut.kbss.jopa.test.environment.Generators; import cz.cvut.kbss.jopa.test.environment.TestEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Set; public final class QueryTestEnvironment { @@ -175,6 +194,9 @@ private static Map, List> generate() { m.put(OWLClassJ.class, generateOwlClassJInstances(aa)); m.put(OWLClassM.class, generateOwlClassMInstances()); m.put(OWLClassT.class, generateOwlClassTInstances(aa)); + final List things = generateThings(); + m.put(Thing.class, things); + m.put(OWLClassV.class, generateOwlClassVInstances(things)); m.put(OWLClassY.class, generateOwlClassYInstances()); return m; } @@ -311,6 +333,34 @@ private static List generateOwlClassMInstances() { return lst; } + private static List generateThings() { + final List lst = new ArrayList<>(); + for (int i = 0; i < ITEM_COUNT; i++) { + final Thing t = new Thing(); + t.setName("Thing-" + i + "-" + i); + t.setDescription(t.getName() + " description"); + lst.add(t); + } + return lst; + } + + private static List generateOwlClassVInstances(List things) { + final List lst = new ArrayList<>(); + for (int i = 0; i< ITEM_COUNT; i++) { + final OWLClassV v = new OWLClassV(); + v.setName("TestV-" + i); + v.setDescription(v.getName() + " description"); + v.setThings(new HashSet<>()); + for (Thing thing : things) { + if (Generators.randomBoolean()) { + v.getThings().add(thing); + } + } + lst.add(v); + } + return lst; + } + private static List generateOwlClassYInstances() { final List lst = new ArrayList<>(); for (int i = 0; i < ITEM_COUNT; i++) { diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java index be02e6e30..a942c5ac7 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java @@ -19,6 +19,8 @@ import cz.cvut.kbss.jopa.exceptions.NoResultException; import cz.cvut.kbss.jopa.exceptions.NoUniqueResultException; +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.Subgraph; import cz.cvut.kbss.jopa.model.annotations.OWLClass; import cz.cvut.kbss.jopa.model.query.TypedQuery; import cz.cvut.kbss.jopa.query.QueryHints; @@ -27,6 +29,8 @@ import cz.cvut.kbss.jopa.test.OWLClassD; import cz.cvut.kbss.jopa.test.OWLClassE; import cz.cvut.kbss.jopa.test.OWLClassM; +import cz.cvut.kbss.jopa.test.OWLClassV; +import cz.cvut.kbss.jopa.test.Thing; import cz.cvut.kbss.jopa.test.Vocabulary; import cz.cvut.kbss.jopa.test.environment.DataAccessor; import cz.cvut.kbss.jopa.test.environment.Generators; @@ -43,6 +47,7 @@ import java.util.Comparator; import java.util.Date; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -53,6 +58,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -450,4 +456,30 @@ public void querySupportsMappingResultsDirectlyToEntity() { assertThat(result.get(i).getTypes(), hasItems(entities.get(i).getTypes().toArray(new String[]{}))); } } + + @Test + public void querySupportsFetchGraphs() { + final EntityGraph fetchGraph = getEntityManager().createEntityGraph(OWLClassV.class); + fetchGraph.addAttributeNodes("name", "description"); + final Subgraph subgraph = fetchGraph.addSubgraph("things"); + subgraph.addAttributeNodes("name"); + final List entities = QueryTestEnvironment.getData(OWLClassV.class); + final List result = getEntityManager().createNativeQuery("SELECT ?x WHERE { ?x a ?type . }", OWLClassV.class) + .setParameter("type", URI.create(Vocabulary.C_OWL_CLASS_V)) + .setHint(QueryHints.FETCH_GRAPH, fetchGraph) + .getResultList(); + assertEquals(entities.size(), result.size()); + result.forEach(c -> { + final Optional expected = entities.stream().filter(v -> v.getUri().equals(c.getUri())).findFirst(); + assertTrue(expected.isPresent()); + assertEquals(expected.get().getName(), c.getName()); + assertEquals(expected.get().getDescription(), c.getDescription()); + assertNotNull(c.getThings()); + assertEquals(expected.get().getThings().size(), c.getThings().size()); + c.getThings().forEach(t -> { + assertNotNull(t.getName()); + assertNull(t.getDescription()); + }); + }); + } } diff --git a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/BugTest.java b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/BugTest.java index 134730856..f254b47b7 100644 --- a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/BugTest.java +++ b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/BugTest.java @@ -312,7 +312,10 @@ void isInferredTriggersLazyLoadingForLazyLoadingProxy() throws Exception { ownerDesc.addAssertion(Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_l_aSetAttribute), false)); final List> ownerAxioms = List.of( new AxiomImpl<>(subject, classAssertion, new Value<>(URI.create(Vocabulary.C_OWL_CLASS_L))), - new AxiomImpl<>(subject, hasAAssertion, new Value<>(NamedResource.create(ref.getUri()))) + new AxiomImpl<>(subject, hasAAssertion, new Value<>(NamedResource.create(ref.getUri()))), + new AxiomImpl<>(subject, Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_l_simpleListAttribute), false), new Value<>(NamedResource.create(ref.getUri()))), + new AxiomImpl<>(subject, Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_l_referencedListAttribute), false), new Value<>(NamedResource.create(ref.getUri()))), + new AxiomImpl<>(subject, Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_l_aSetAttribute), false), new Value<>(NamedResource.create(ref.getUri()))) ); when(connectionMock.find(ownerDesc)).thenReturn(ownerAxioms); final AxiomDescriptor singleAAttDesc = new AxiomDescriptor(subject); diff --git a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/FetchGraphEntityLoadingTest.java b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/FetchGraphEntityLoadingTest.java new file mode 100644 index 000000000..a1d46facb --- /dev/null +++ b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/FetchGraphEntityLoadingTest.java @@ -0,0 +1,148 @@ +package cz.cvut.kbss.jopa.test.integration; + +import cz.cvut.kbss.jopa.model.EntityGraph; +import cz.cvut.kbss.jopa.model.Subgraph; +import cz.cvut.kbss.jopa.query.QueryHints; +import cz.cvut.kbss.jopa.test.OWLClassA; +import cz.cvut.kbss.jopa.test.OWLClassD; +import cz.cvut.kbss.jopa.test.OWLClassS; +import cz.cvut.kbss.jopa.test.OWLClassU; +import cz.cvut.kbss.jopa.test.Vocabulary; +import cz.cvut.kbss.jopa.test.environment.Generators; +import cz.cvut.kbss.ontodriver.ResultSet; +import cz.cvut.kbss.ontodriver.Statement; +import cz.cvut.kbss.ontodriver.exception.OntoDriverException; +import cz.cvut.kbss.ontodriver.iteration.ResultRow; +import cz.cvut.kbss.ontodriver.iteration.ResultSetIterator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.net.URI; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class FetchGraphEntityLoadingTest extends IntegrationTestBase { + + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + } + + @Test + void fetchGraphBasedQueryResultLoadingWorks() throws OntoDriverException { + when(connectionMock.getRepositoryMetadata()).thenReturn(() -> ""); + final OWLClassD expected = new OWLClassD(Generators.generateUri()); + final OWLClassA expectedA = Generators.generateOwlClassA(); + expected.setOwlClassA(expectedA); + mockQueryResultSet(expected); + final EntityGraph fetchGraph = em.createEntityGraph(OWLClassD.class); + final Subgraph subgraph = fetchGraph.addSubgraph("owlClassA"); + subgraph.addAttributeNodes("stringAttribute"); + + final List result = em.createQuery("SELECT d FROM OWLClassD d", OWLClassD.class) + .setHint(QueryHints.FETCH_GRAPH, fetchGraph) + .getResultList(); + assertEquals(1, result.size()); + assertEquals(expected.getUri(), result.get(0).getUri()); + assertNotNull(result.get(0).getOwlClassA()); + assertEquals(expectedA.getUri(), result.get(0).getOwlClassA().getUri()); + assertEquals(expectedA.getStringAttribute(), result.get(0).getOwlClassA().getStringAttribute()); + verify(connectionMock, never()).find(any()); + } + + private void mockQueryResultSet(OWLClassD expected) throws OntoDriverException { + final Statement statementMock = mock(Statement.class); + final ResultSet resultSetMock = mock(ResultSet.class); + final ResultSetIterator resultSetIteratorMock = mock(ResultSetIterator.class); + final ResultRow resultRowMock = mock(ResultRow.class); + when(connectionMock.createStatement()).thenReturn(statementMock); + when(statementMock.executeQuery(anyString())).thenReturn(resultSetMock); + when(resultSetMock.iterator()).thenReturn(resultSetIteratorMock); + when(resultSetIteratorMock.next()).thenReturn(resultRowMock); + when(resultSetIteratorMock.hasNext()).thenReturn(true, false); + when(resultRowMock.getColumnCount()).thenReturn(5); + when(resultRowMock.isBound("x")).thenReturn(true); + when(resultRowMock.getObject("x", URI.class)).thenReturn(expected.getUri()); + when(resultRowMock.getObject(0, URI.class)).thenReturn(expected.getUri()); + when(resultRowMock.isBound("x_types")).thenReturn(true); + when(resultRowMock.getObject("x_types")).thenReturn(URI.create(Vocabulary.C_OWL_CLASS_D)); + when(resultRowMock.isBound("x_owlClassA")).thenReturn(true); + when(resultRowMock.getObject("x_owlClassA")).thenReturn(expected.getOwlClassA().getUri()); + when(resultRowMock.getObject("x_owlClassA", URI.class)).thenReturn(expected.getOwlClassA().getUri()); + when(resultRowMock.isBound("x_owlClassA_types_gc")).thenReturn(true); + when(resultRowMock.getString("x_owlClassA_types_gc")).thenReturn(Vocabulary.C_OWL_CLASS_A); + when(resultRowMock.isBound("x_owlClassA_stringAttribute")).thenReturn(true); + when(resultRowMock.getObject("x_owlClassA_stringAttribute")).thenReturn(expected.getOwlClassA() + .getStringAttribute()); + } + + @Test + void fetchGraphBasedQueryResultLoadingDoesNotLoadAttributesNotSpecifiedInFetchGraph() throws OntoDriverException { + when(connectionMock.getRepositoryMetadata()).thenReturn(() -> ""); + final OWLClassU expected = new OWLClassU(); + expected.setUri(Generators.generateUri()); + expected.setName("test"); + expected.setDescription("test description"); + final OWLClassS reference = new OWLClassS(); + reference.setUri(Generators.generateUri()); + reference.setName("test reference"); + reference.setDescription("test reference description"); + expected.setOwlClassS(reference); + mockQueryResultSet(expected); + final EntityGraph fetchGraph = em.createEntityGraph(OWLClassU.class); + fetchGraph.addAttributeNodes("name"); + final Subgraph subgraph = fetchGraph.addSubgraph("owlClassS"); + subgraph.addAttributeNodes("name"); + + final List result = em.createQuery("SELECT u FROM OWLClassU u", OWLClassU.class) + .setHint(QueryHints.FETCH_GRAPH, fetchGraph) + .getResultList(); + assertEquals(1, result.size()); + assertEquals(expected.getUri(), result.get(0).getUri()); + assertEquals(expected.getName(), result.get(0).getName()); + assertNull(result.get(0).getDescription()); + assertNotNull(result.get(0).getOwlClassS()); + assertEquals(reference.getUri(), result.get(0).getOwlClassS().getUri()); + assertEquals(reference.getName(), result.get(0).getOwlClassS().getName()); + assertNull(result.get(0).getOwlClassS().getDescription()); + } + + private void mockQueryResultSet(OWLClassU expected) throws OntoDriverException { + final Statement statementMock = mock(Statement.class); + final ResultSet resultSetMock = mock(ResultSet.class); + final ResultSetIterator resultSetIteratorMock = mock(ResultSetIterator.class); + final ResultRow resultRowMock = mock(ResultRow.class); + when(connectionMock.createStatement()).thenReturn(statementMock); + when(statementMock.executeQuery(anyString())).thenReturn(resultSetMock); + when(resultSetMock.iterator()).thenReturn(resultSetIteratorMock); + when(resultSetIteratorMock.next()).thenReturn(resultRowMock); + when(resultSetIteratorMock.hasNext()).thenReturn(true, false); + when(resultRowMock.getColumnCount()).thenReturn(6); + when(resultRowMock.isBound("x")).thenReturn(true); + when(resultRowMock.getObject("x", URI.class)).thenReturn(expected.getUri()); + when(resultRowMock.getObject(0, URI.class)).thenReturn(expected.getUri()); + when(resultRowMock.isBound("x_name")).thenReturn(true); + when(resultRowMock.getObject("x_name")).thenReturn(expected.getName()); + when(resultRowMock.isBound("x_types")).thenReturn(true); + when(resultRowMock.getObject("x_types")).thenReturn(URI.create(Vocabulary.C_OWL_CLASS_U)); + when(resultRowMock.isBound("x_owlClassS")).thenReturn(true); + when(resultRowMock.getObject("x_owlClassS")).thenReturn(expected.getOwlClassS().getUri()); + when(resultRowMock.getObject("x_owlClassS", URI.class)).thenReturn(expected.getOwlClassS().getUri()); + when(resultRowMock.isBound("x_owlClassS_name")).thenReturn(true); + when(resultRowMock.getObject("x_owlClassS_name")).thenReturn(expected.getOwlClassS().getName()); + when(resultRowMock.isBound("x_owlClassS_types_gc")).thenReturn(true); + when(resultRowMock.getString("x_owlClassS_types_gc")).thenReturn(Vocabulary.C_OWL_CLASS_S); + } +}