diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index f95f3ba9b..b7308f9d8 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -4,7 +4,7 @@ on: push: branches: [ master ] pull_request: - branches: [ master, development ] + branches: [ master, development, release/** ] jobs: build: @@ -13,10 +13,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: 11 + java-version: 17 - name: Build with Maven run: mvn -B package --file pom.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index b3e681290..963da1365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # JOPA - Change Log +## 2.0.0 - 2024-05-27 +- Move internal API from `jopa-api` to the `jopa-impl` module (Enhancement #146). +- Modify name resolution in OWL2Java, support prefixes so that terms are better disambiguated without appending the useless `_A` suffix if possible (Enhancement #85). +- Remove dependency on AspectJ by rewriting object change tracking and lazy loading (Enhancement #145, #231). See the [wiki](https://github.com/kbss-cvut/jopa/wiki/Change-Tracking-and-Lazy-Loading) for details. +- Add support for RDF collections (Enhancement #51). +- Update dependencies (Jena 5.0.0, RDF4J 4.3.11). +- Require Java 17. + ## 1.2.2 - 2024-01-30 - Modify `DefaultClasspathScanner` to handle Spring Boot nested JAR introduced in 3.2.0 (Bug #227). diff --git a/README.md b/README.md index 882d6bae5..10c1bca9b 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ The library architecture and API is similar to JPA (see [2]) so that Java develo * Explicit access to inferred knowledge * Access to unmapped properties and individual's types * Transactions +* Multilingual strings * Separate storage access layer - Jena, OWLAPI, RDF4J drivers are available #### Object-ontological Mapping Based on Integrity Constraints @@ -23,13 +24,13 @@ Similarly to object-relational mapping (ORM), OOM enables to map ontological con More specifically, OOM in JOPA maps (using the JLS [3] terminology): -| Ontology | OO Language | -|---------------------|------------------------------------------| -| OWL Class | Reference type | -| Object property | Reference type member | -| Data property | Primitive type member (+ String, Date) | -| Annotation property | Reference or primitive type member | -| Class assertions | Reference type instance or @Types record | +| Ontology | OO Language | +|---------------------|------------------------------------------------------------| +| OWL Class | Reference type | +| Object property | Reference type member | +| Data property | Primitive type member (+ String, Date, MultilingualString) | +| Annotation property | Reference or primitive type member | +| Class assertions | Reference type instance or @Types record | All this means that individuals belonging to an OWL class can be retrieved as instances of a (Java) class. @@ -101,15 +102,6 @@ Supported storages: * [OWLAPI](https://github.com/owlcs/owlapi) * [RDF4J](https://rdf4j.org/) -### Not Supported, yet - -JOPA currently does not support referential integrity. This, for example, means that removing an instance that is referenced -by another instance is possible even though it should not. -Such feature is vital for object-oriented application, but not compatible with -the open-world nature of ontologies. Design possibilities and their implications are currently being studied. - -Other missing/planned stuff can be found in the GitHub issue tracker and in [TODO.md](TODO.md). - ## Modules The whole framework consists of several modules: @@ -141,14 +133,14 @@ JOPA examples can be found in a separate repository at [https://github.com/kbss- A real-world, up-to-date project using JOPA is [TermIt](https://github.com/kbss-cvut/termit) - a SKOS-compatible vocabulary manager. -Note that JOPA requires **Java 11** or later. +Note that JOPA requires **Java 17** or later. ## Getting JOPA There are two ways of getting JOPA for a project: * Clone repository/download zip and build it with Maven, -* Use a Maven dependency from the [Maven central repository](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22cz.cvut.kbss.jopa%22). +* Use a Maven/Gradle dependency from the [Maven central repository](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22cz.cvut.kbss.jopa%22). Basically, the _jopa-impl_ module and one of the OntoDriver implementations is all that is needed: @@ -169,7 +161,7 @@ Basically, the _jopa-impl_ module and one of the OntoDriver implementations is a ## More Info -More information about JOPA can be found for example in articles [4], [5], [6] and on the GitHub [Wiki](https://github.com/kbss-cvut/jopa/wiki). +More information about JOPA can be found, for example, in articles [4], [5], [6] and on the GitHub [Wiki](https://github.com/kbss-cvut/jopa/wiki). JOPA build status and code metrics can be found at: @@ -195,6 +187,7 @@ Some related libraries: Notable changes: +* **2.0.0** - Major rewrite of change tracking and lazy loading, remove internal API from the `jopa-api` module etc. * **1.0.0** - Support for static metamodel generation and mapping multiple inheritance via Java interfaces. * **0.20.0** - Allow editing inferred attributes (See the [wiki](https://github.com/kbss-cvut/jopa/wiki) for more details). Support `IN`, `NOT LIKE`, `<>` operators in SOQL. * **0.19.0** - Add RDF4J driver (renaming of Sesame driver, which has been deprecated and will be removed in the future) diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 76b45bb6e..000000000 --- a/TODO.md +++ /dev/null @@ -1,17 +0,0 @@ -# JOPA TODOs - -## High Priority - -- [ ] Allow static (annotation, attribute of `@OWLDataProperty`) specification of language of String attributes. - This can be overridden on EM operation level (in descriptor). - - -## Low Priority - -- [ ] How to enhance query results with transactional changes? RDF4J driver - - First check how SQL queries in JPA behave -- [ ] Add possibility to generate integrity constraints from the object model - -## Research Topics - -- [ ] Data integrity violation checks. E.g. when an entity is removed, there should be a check whether it is referenced from another entity diff --git a/datatype/pom.xml b/datatype/pom.xml index 6f586efc8..4c6a3e404 100644 --- a/datatype/pom.xml +++ b/datatype/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 1.2.2 + 2.0.0 ../pom.xml diff --git a/datatype/src/main/java/cz/cvut/kbss/jopa/datatype/util/Pair.java b/datatype/src/main/java/cz/cvut/kbss/jopa/datatype/util/Pair.java index c2d738cfd..6344a97d5 100644 --- a/datatype/src/main/java/cz/cvut/kbss/jopa/datatype/util/Pair.java +++ b/datatype/src/main/java/cz/cvut/kbss/jopa/datatype/util/Pair.java @@ -17,30 +17,11 @@ */ package cz.cvut.kbss.jopa.datatype.util; -import java.util.AbstractMap; - /** * Represents an ordered pair of values. * * @param Type of the first value in the pair * @param Type of the second value in the pair */ -public final class Pair extends AbstractMap.SimpleEntry { - - public Pair(F first, S second) { - super(first, second); - } - - public F getFirst() { - return getKey(); - } - - public S getSecond() { - return getValue(); - } - - @Override - public String toString() { - return "(" + getFirst() + ", " + getSecond() + ")"; - } +public record Pair(F first, S second) { } diff --git a/jopa-api/pom.xml b/jopa-api/pom.xml index 6f1e794f6..2712eccc8 100644 --- a/jopa-api/pom.xml +++ b/jopa-api/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/NonJPA.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/NonJPA.java index 19d14a0da..a50232db3 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/NonJPA.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/NonJPA.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa; /** - * Denotes class/member that is not present in JPA 2.0 + * Denotes class/member that is not present in JPA (Jakarta Persistence) */ public @interface NonJPA { } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/InvalidAssertionIdentifierException.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/InvalidAssertionIdentifierException.java index 1726cd3b6..80d17b08b 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/InvalidAssertionIdentifierException.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/InvalidAssertionIdentifierException.java @@ -22,15 +22,8 @@ */ public class InvalidAssertionIdentifierException extends OWLPersistenceException { - public InvalidAssertionIdentifierException(String message) { - super(message); - } - public InvalidAssertionIdentifierException(String message, Throwable cause) { super(message, cause); } - public InvalidAssertionIdentifierException(Throwable cause) { - super(cause); - } } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/NoResultException.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/NoResultException.java index aac0adbd6..4c5c6c903 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/NoResultException.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/NoResultException.java @@ -28,8 +28,6 @@ */ public class NoResultException extends OWLPersistenceException { - private static final long serialVersionUID = -1891852675684320722L; - public NoResultException(String message) { super(message); } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/NoUniqueResultException.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/NoUniqueResultException.java index 8966c7100..6f2d0942c 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/NoUniqueResultException.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/NoUniqueResultException.java @@ -28,8 +28,6 @@ */ public class NoUniqueResultException extends OWLPersistenceException { - private static final long serialVersionUID = -6340735954399074847L; - public NoUniqueResultException(String message) { super(message); } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/OWLEntityExistsException.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/OWLEntityExistsException.java index 215275f14..1f8acaa5a 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/OWLEntityExistsException.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/OWLEntityExistsException.java @@ -22,8 +22,6 @@ */ public class OWLEntityExistsException extends OWLPersistenceException { - private static final long serialVersionUID = 453666323423782580L; - public OWLEntityExistsException(String message) { super(message); } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/RollbackException.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/RollbackException.java index 662f69b97..c2d584d7c 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/RollbackException.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/RollbackException.java @@ -24,8 +24,6 @@ */ public class RollbackException extends OWLPersistenceException { - private static final long serialVersionUID = 8371285315001388603L; - public RollbackException(String message) { super(message); } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/StorageAccessException.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/StorageAccessException.java index 6e92ca574..abf76eb2b 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/StorageAccessException.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/StorageAccessException.java @@ -22,8 +22,6 @@ */ public class StorageAccessException extends OWLPersistenceException { - private static final long serialVersionUID = 4661531292404254252L; - public StorageAccessException(String message) { super(message); } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/AnnotationValue.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/AnnotationValue.java deleted file mode 100644 index 1cd0eb47c..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/AnnotationValue.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.model; - -public interface AnnotationValue { - -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/Cache.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/Cache.java similarity index 97% rename from jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/Cache.java rename to jopa-api/src/main/java/cz/cvut/kbss/jopa/model/Cache.java index 4fa9d773e..eea855268 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/Cache.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/Cache.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.sessions; +package cz.cvut.kbss.jopa.model; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; @@ -26,8 +26,6 @@ *

* If a cache is not in use, the methods of this interface have no effect, except for contains, which * returns false. - *

- * Taken from JPA 2. */ public interface Cache { 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 2edcd370d..2c71c68b9 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 @@ -27,14 +27,14 @@ import cz.cvut.kbss.jopa.model.query.Query; import cz.cvut.kbss.jopa.model.query.TypedQuery; import cz.cvut.kbss.jopa.model.query.criteria.CriteriaQuery; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import cz.cvut.kbss.jopa.transactions.EntityTransaction; import java.net.URI; import java.util.List; import java.util.Map; -public interface EntityManager { +public interface EntityManager extends AutoCloseable { /** * Make an instance managed and persistent. @@ -140,14 +140,7 @@ public interface EntityManager { * null} * @see #getContexts() */ - T find(final Class entityClass, final Object identifier, - final Descriptor descriptor); - - // TODO JPA 2.0 find with properties - - // TODO JPA 2.0 find with lock mode - - // TODO JPA 2.0 find with lock mode and properties + T find(final Class entityClass, final Object identifier, final Descriptor descriptor); /** * Get an instance, whose state may be lazily fetched. @@ -192,33 +185,6 @@ T find(final Class entityClass, final Object identifier, */ void flush(); - // /** - // * Set the flush mode that applies to all objects contained in the - // * persistence context. - // * - // * @param flushMode - // */ - // public void setFlushMode(FlushModeType flushMode); - - // TODO JPA 2.0 getFlushMode - - // /** - // * Set the lock mode for an entity object contained in the persistence - // * context. - // * - // * @param entity - // * @param lockMode - // * @throws PersistenceException - // * if an unsupported lock call is made - // * @throws IllegalArgumentException - // * if the instance is not an entity or is a detached entity - // * @throws TransactionRequiredException - // * if there is no transaction - // */ - // public void lock(Object entity, LockModeType lockMode); - - // TODO JPA 2.0 lock with lock mode and properties - /** * Refresh the state of the instance from the data source, overwriting changes made to the entity, if any. * @@ -229,10 +195,6 @@ T find(final Class entityClass, final Object identifier, */ void refresh(final Object entity); - // TODO JPA 2.0 refresh with lock mode - // TODO JPA 2.0 refresh with properties - // TODO JPA 2.0 refresh with lock mode and properties - /** * Clear the persistence context, causing all managed entities to become detached. Changes made to entities that * have not been flushed to the database will not be persisted. @@ -285,8 +247,6 @@ T find(final Class entityClass, final Object identifier, */ boolean isInferred(T entity, FieldSpecification attribute, Object value); - // TODO JPA 2.0 public LockModeType getLockMode(Object entity) - /** * Get the properties and hints and associated values that are in effect for the entity manager. *

diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactory.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactory.java index b59cd2d1d..f71499178 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactory.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactory.java @@ -20,15 +20,15 @@ import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; import cz.cvut.kbss.jopa.model.metamodel.Metamodel; import cz.cvut.kbss.jopa.model.query.Query; -import cz.cvut.kbss.jopa.sessions.Cache; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaQuery; import java.util.Map; -public interface EntityManagerFactory { +public interface EntityManagerFactory extends AutoCloseable { /** - * Create a new application-managed EntityManager. This method returns a new - * EntityManager instance each time it is invoked. The isOpen method will - * return true on the returned instance. + * Create a new application-managed EntityManager. This method returns a new EntityManager instance each time it is + * invoked. The isOpen method will return true on the returned instance. * * @return entity manager instance * @throws IllegalStateException if the entity manager factory has been closed @@ -36,20 +36,24 @@ public interface EntityManagerFactory { EntityManager createEntityManager(); /** - * Create a new EntityManager with the specified Map of properties. This - * method returns a new EntityManager instance each time it is invoked. The - * isOpen method will return true on the returned instance. + * Create a new EntityManager with the specified Map of properties. This method returns a new EntityManager instance + * each time it is invoked. The isOpen method will return true on the returned instance. * * @param map properties for entity manager * @return entity manager instance */ EntityManager createEntityManager(Map map); - // TODO JPA 2.0 getCriteriaBuilder + /** + * Returns an instance of {@link CriteriaBuilder} which may be used to construct {@link CriteriaQuery} objects. + * + * @return an instance of {@code CriteriaBuilder} + * @throws IllegalStateException if the entity manager factory has been closed + */ + CriteriaBuilder getCriteriaBuilder(); /** - * Return an instance of Metamodel interface for access to the metamodel of - * the persistence unit. + * Return an instance of Metamodel interface for access to the metamodel of the persistence unit. * * @return Metamodel instance * @throws IllegalStateException if the entity manager factory has been closed @@ -57,19 +61,16 @@ public interface EntityManagerFactory { Metamodel getMetamodel(); /** - * Close the factory, releasing any resources that it holds. After a factory - * instance is closed, all methods invoked on it will throw an - * IllegalStateException, except for isOpen, which will return false. Once - * an EntityManagerFactory has been closed, all its entity managers are - * considered to be in the closed state. + * Close the factory, releasing any resources that it holds. After a factory instance is closed, all methods invoked + * on it will throw an IllegalStateException, except for isOpen, which will return false. Once an + * EntityManagerFactory has been closed, all its entity managers are considered to be in the closed state. * * @throws IllegalStateException if the entity manager factory has been closed */ void close(); /** - * Indicates whether the factory is open. Returns true until the factory has - * been closed. + * Indicates whether the factory is open. Returns true until the factory has been closed. * * @return true if the entity manager is open * @throws IllegalStateException if the entity manager factory has been closed @@ -87,8 +88,7 @@ public interface EntityManagerFactory { Map getProperties(); /** - * Access the cache that is associated with the entity manager factory (the - * "second level cache"). + * Access the cache that is associated with the entity manager factory (the "second level cache"). * * @return instance of the Cache interface * @throws IllegalStateException if the entity manager factory has been closed diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/IRI.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/IRI.java index 6db16ab37..1ee1c5eb4 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/IRI.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/IRI.java @@ -20,7 +20,7 @@ import java.net.URI; import java.util.Objects; -public class IRI implements AnnotationValue { +public class IRI { private final String value; diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/Literal.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/Literal.java deleted file mode 100644 index 7bd30ba01..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/Literal.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.model; - -public interface Literal extends AnnotationValue { - - String getLang(); - - T getValue(); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/DomainOf.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/DomainOf.java deleted file mode 100644 index 7a0c08d5c..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/DomainOf.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.model.annotations; - -import java.lang.annotation.*; - -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface DomainOf { - /** - * IRI of the object/data property - * - * @return IRI of the object/data property - */ - String owlPropertyIRI(); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/DomainRangeConstraints.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/DomainRangeConstraints.java deleted file mode 100644 index 3167f97c2..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/DomainRangeConstraints.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.model.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface DomainRangeConstraints { - - DomainOf[] domainOf() default {}; - - RangeOf[] rangeOf() default {}; -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/RDFCollection.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/RDFCollection.java new file mode 100644 index 000000000..e241deeb4 --- /dev/null +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/RDFCollection.java @@ -0,0 +1,18 @@ +package cz.cvut.kbss.jopa.model.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies mapping of an RDF collection. + *

+ * RDF collections are lists of items where each item points to a value and the next item. In this regard, they are + * equivalent to a {@link Sequence} of type {@link SequenceType#referenced}. In contrast to referenced lists though, RDF + * collections are closed by a {@literal rdf:nil} element. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface RDFCollection { +} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/RangeOf.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/RangeOf.java deleted file mode 100644 index f9a3846f8..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/RangeOf.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.model.annotations; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface RangeOf { - - /** - * IRI of the object property - * - * @return IRI of the object property - */ - String owlPropertyIRI(); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/Sequence.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/Sequence.java index 982f9f002..31823c209 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/Sequence.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/Sequence.java @@ -30,7 +30,7 @@ */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD,ElementType.METHOD}) +@Target({ElementType.FIELD, ElementType.METHOD}) public @interface Sequence { /** @@ -41,27 +41,27 @@ SequenceType type() default SequenceType.referenced; /** - * URI of the class that represents the 'OWLList' concept. - * - * Relevant only for REFERENCED type. + * IRI of the class that represents the 'OWLList' concept. + *

+ * Relevant only for {@link SequenceType#referenced} type. * * @return OWLList class IRI */ - String ClassOWLListIRI() default SequencesVocabulary.s_c_OWLList; + String listClassIRI() default SequencesVocabulary.s_c_OWLList; /** - * URI of the object property that represents the 'hasContents' role. - * - * Relevant only for REFERENCED type. + * IRI of the object property that represents the 'hasContents' role. + *

+ * Relevant only for {@link SequenceType#referenced} type. * * @return hasContents property IRI */ - String ObjectPropertyHasContentsIRI() default SequencesVocabulary.s_p_hasContents; + String hasContentsPropertyIRI() default SequencesVocabulary.s_p_hasContents; /** * URI of the object property that represents the 'hasNext' role. * * @return hasNext property IRI */ - String ObjectPropertyHasNextIRI() default SequencesVocabulary.s_p_hasNext; + String hasNextPropertyIRI() default SequencesVocabulary.s_p_hasNext; } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/SequenceType.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/SequenceType.java index dc714d9b8..6f988108e 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/SequenceType.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/SequenceType.java @@ -22,10 +22,10 @@ */ public enum SequenceType { /** - * Used for simple (nonreferenced) sequences. + * Used for simple (non-referenced) sequences. *

* This means that elements of the sequence are unique to the sequence owner and are NOT shared with other - * sequences. + * sequences. It also means that only resources can be values of a simple sequence. *

* Example: *

@@ -36,15 +36,15 @@ public enum SequenceType {
 
     /**
      * Used for referenced sequences. This case is more general, but sequence representation requires more space (linear
-     * in the original size)
+     * in the original size).
      * 

- * This means that elements of the sequence are not unique to the sequence owner. Thus these elements might be - * referenced by other sequences. + * This means that elements of the sequence are not unique to the sequence owner. Thus, these elements might be + * referenced by other sequences. It also means that elements of the sequence can be literals as well as resources. *

* Example: *

      * entity - nodeOne - nodeTwo - nodeThree
-     *              |          |           |
+     *               |         |           |
      *           itemOne    itemTwo    itemThree
      * 
*/ diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/Types.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/Types.java index e1063d762..ffe4b167e 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/Types.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/Types.java @@ -17,7 +17,11 @@ */ package cz.cvut.kbss.jopa.model.annotations; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.Set; /** @@ -32,7 +36,7 @@ @Target(ElementType.FIELD) public @interface Types { - FetchType fetchType() default FetchType.LAZY; + FetchType fetchType() default FetchType.EAGER; /** * Denotes a member that is inferred (true) using the OWL reasoner or just asserted (false). diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptor.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptor.java index cae412ab8..5aa27663f 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptor.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptor.java @@ -21,12 +21,16 @@ import cz.cvut.kbss.jopa.model.metamodel.Attribute.PersistentAttributeType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.QueryAttribute; -import cz.cvut.kbss.jopa.utils.ErrorUtils; import java.lang.reflect.Field; import java.net.URI; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; /** * Describes an entity. @@ -42,7 +46,7 @@ public EntityDescriptor() { } /** - * Allows to configure where object property assertions should be stored + * Allows configuring where object property assertions should be stored * * @param assertionsInSubjectContext Whether object property assertions are stored in the subject's. Defaults to * {@code true}. If {@code false}, object property assertions are stored in the @@ -71,8 +75,8 @@ public EntityDescriptor(URI context, boolean assertionsInSubjectContext) { @Override public EntityDescriptor addAttributeDescriptor(FieldSpecification attribute, Descriptor descriptor) { - Objects.requireNonNull(attribute, ErrorUtils.getNPXMessageSupplier("attribute")); - Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor")); + Objects.requireNonNull(attribute); + Objects.requireNonNull(descriptor); fieldDescriptors.put(attribute.getJavaField(), descriptor); return this; @@ -80,7 +84,7 @@ public EntityDescriptor addAttributeDescriptor(FieldSpecification attribut @Override public EntityDescriptor addAttributeContext(FieldSpecification attribute, URI context) { - Objects.requireNonNull(attribute, ErrorUtils.getNPXMessageSupplier("attribute")); + Objects.requireNonNull(attribute); fieldDescriptors.putIfAbsent(attribute.getJavaField(), createDescriptor(attribute, context != null ? Collections.singleton(context) : Collections.emptySet())); diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/Attribute.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/Attribute.java index f7a204e2e..1c00b6408 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/Attribute.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/Attribute.java @@ -18,7 +18,6 @@ package cz.cvut.kbss.jopa.model.metamodel; import cz.cvut.kbss.jopa.NonJPA; -import cz.cvut.kbss.jopa.UnusedJPA; import cz.cvut.kbss.jopa.model.IRI; import cz.cvut.kbss.jopa.model.annotations.CascadeType; import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraint; @@ -63,7 +62,6 @@ enum PersistentAttributeType { * * @return corresponding java.lang.reflect.Member */ - @UnusedJPA java.lang.reflect.Member getJavaMember(); /** diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/FieldSpecification.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/FieldSpecification.java index 7ed4fb13d..c5ddefa59 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/FieldSpecification.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/FieldSpecification.java @@ -87,4 +87,4 @@ public interface FieldSpecification { * @return boolean indicating whether the attribute is collection-valued */ boolean isCollection(); -} \ No newline at end of file +} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableType.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableType.java index feace8811..1ccd62666 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableType.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableType.java @@ -18,7 +18,6 @@ package cz.cvut.kbss.jopa.model.metamodel; import cz.cvut.kbss.jopa.NonJPA; -import cz.cvut.kbss.jopa.UnusedJPA; import java.util.Set; @@ -29,7 +28,6 @@ * @param * The represented entity or mapped superclass type. */ -@UnusedJPA public interface IdentifiableType extends ManagedType { /** diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifierVisitor.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifierVisitor.java index 02d91842c..4cfdeb141 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifierVisitor.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifierVisitor.java @@ -20,8 +20,4 @@ public interface IdentifierVisitor { void visit(IRIIdentifier i); - - void visit(KeyIdentifier i); - - void visit(SameAsIdentifier i); } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/KeyIdentifier.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/KeyIdentifier.java deleted file mode 100644 index 9d14fa9f0..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/KeyIdentifier.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.model.metamodel; - -import java.util.Set; - -import cz.cvut.kbss.jopa.model.IRI; - -public interface KeyIdentifier extends Identifier { - - Set getOWLPropertyIRIs(); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ListAttribute.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ListAttribute.java index e9ef6efcc..909997434 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ListAttribute.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ListAttribute.java @@ -22,18 +22,16 @@ import cz.cvut.kbss.jopa.model.annotations.SequenceType; /** - * Instances of the type ListAttribute represent persistent - * java.util.List-valued attributes. + * Instances of the type ListAttribute represent persistent {@link java.util.List}-valued attributes. * - * @param - * The type the represented List belongs to - * @param - * The element type of the represented List + * @param The type the represented List belongs to + * @param The element type of the represented List */ public interface ListAttribute extends PluralAttribute, E> { /** * Gets the type of the sequence. + * * @return List type */ @NonJPA @@ -41,26 +39,39 @@ public interface ListAttribute extends PluralAttribute * This is relevant only for referenced lists. + * * @return List type IRI */ @NonJPA - IRI getOWLListClass(); + IRI getListClassIRI(); /** * Gets IRI of the property representing the relation between a list node and its content (value). - * + *

* Relevant only for referenced lists. + * * @return Property IRI */ @NonJPA - IRI getOWLPropertyHasContentsIRI(); + IRI getHasContentsPropertyIRI(); /** * Gets IRI of the property representing next node in the list. + * * @return Property IRI */ @NonJPA - IRI getOWLObjectPropertyHasNextIRI(); + IRI getHasNextPropertyIRI(); + + /** + * Whether this list represents a RDF + * collection. + * + * @return {@code true} when this list attribute is an RDF collection, {@code false} otherwise + * @see cz.cvut.kbss.jopa.model.annotations.RDFCollection + */ + @NonJPA + boolean isRDFCollection(); } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MappedSuperclassType.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MappedSuperclassType.java index 562cdb32b..7e88f737d 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MappedSuperclassType.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MappedSuperclassType.java @@ -17,13 +17,10 @@ */ package cz.cvut.kbss.jopa.model.metamodel; -import cz.cvut.kbss.jopa.UnusedJPA; - /** * Instances of the type MappedSuperclassType represent mapped superclass types. * * @param The represented entity type */ -@UnusedJPA public interface MappedSuperclassType extends IdentifiableType { } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/QueryAttribute.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/QueryAttribute.java index a1392bb1a..a972b12c6 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/QueryAttribute.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/QueryAttribute.java @@ -18,7 +18,6 @@ package cz.cvut.kbss.jopa.model.metamodel; import cz.cvut.kbss.jopa.NonJPA; -import cz.cvut.kbss.jopa.UnusedJPA; import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraint; /** @@ -50,7 +49,6 @@ public interface QueryAttribute extends FieldSpecification { * * @return corresponding java.lang.reflect.Member */ - @UnusedJPA java.lang.reflect.Member getJavaMember(); /** diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SameAsIdentifier.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SameAsIdentifier.java deleted file mode 100644 index f64f8b2ca..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SameAsIdentifier.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.model.metamodel; - -public interface SameAsIdentifier extends Identifier { -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SingularAttribute.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SingularAttribute.java index 8687c1f1d..169a8cd49 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SingularAttribute.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SingularAttribute.java @@ -43,10 +43,8 @@ public interface SingularAttribute extends Attribute, Bindable { * Is the attribute a version attribute. * * @return boolean indicating whether the attribute is a version attribute - * @deprecated Not supported in JOPA */ @UnusedJPA - @Deprecated boolean isVersion(); /** diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/Tuple.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/Tuple.java deleted file mode 100644 index 536655c39..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/Tuple.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.model.query; - -import java.util.List; - -/** - * Interface for extracting the elements of a query result tuple. - */ -public interface Tuple { - - /** - * Get the value of the specified tuple element. - * - * @param tupleElement - * tuple element - * @return value of tuple element - * @throws IllegalArgumentException - * if tuple element - * - * does not correspond to an element in the - * - * query result tuple - */ - X get(TupleElement tupleElement); - - /** - * Get the value of the tuple element to which the specified alias has been - * assigned. - * - * @param alias - * alias assigned to tuple element - * @param type - * of the tuple element - * @return value of the tuple element - * @throws IllegalArgumentException - * if alias - * - * does not correspond to an element in the - * - * query result tuple or element cannot be - * - * assigned to the specified type - */ - X get(String alias, Class type); - - /** - * Get the value of the tuple element to which the specified alias has been - * assigned. - * - * @param alias - * alias assigned to tuple element - * @return value of the tuple element - * @throws IllegalArgumentException - * if alias - * - * does not correspond to an element in the - * - * query result tuple - */ - Object get(String alias); - - /** - * Get the value of the element at the specified position in the result - * tuple. The first position is 0. - * - * @param i - * position in result tuple - * @param type - * type of the tuple element - * @return value of the tuple element - * @throws IllegalArgumentException - * if i exceeds - * - * length of result tuple or element cannot be - * - * assigned to the specified type - */ - X get(int i, Class type); - - /** - * Get the value of the element at the specified position in the result - * tuple. The first position is 0. - * - * @param i - * position in result tuple - * @return value of the tuple element - * @throws IllegalArgumentException - * if i exceeds - * - * length of result tuple - */ - Object get(int i); - - /** - * Return the values of the result tuple elements as an array. - * - * @return tuple element values - */ - Object[] toArray(); - - /** - * Return the tuple elements. - * - * @return tuple elements - */ - List> getElements(); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CriteriaBuilder.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaBuilder.java similarity index 93% rename from jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CriteriaBuilder.java rename to jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaBuilder.java index 43e9d42bc..36f1de8dc 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CriteriaBuilder.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaBuilder.java @@ -15,13 +15,7 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.sessions; - -import cz.cvut.kbss.jopa.model.query.criteria.CriteriaQuery; -import cz.cvut.kbss.jopa.model.query.criteria.Expression; -import cz.cvut.kbss.jopa.model.query.criteria.Order; -import cz.cvut.kbss.jopa.model.query.criteria.ParameterExpression; -import cz.cvut.kbss.jopa.model.query.criteria.Path; +package cz.cvut.kbss.jopa.model.query.criteria; /** * Used to construct criteria queries, compound selections, expressions, predicates, orderings. diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/PredicateFactory.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/PredicateFactory.java similarity index 98% rename from jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/PredicateFactory.java rename to jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/PredicateFactory.java index 63281a937..ce149963d 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/PredicateFactory.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/PredicateFactory.java @@ -15,10 +15,7 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.sessions; - -import cz.cvut.kbss.jopa.model.query.criteria.Expression; -import cz.cvut.kbss.jopa.model.query.criteria.Predicate; +package cz.cvut.kbss.jopa.model.query.criteria; import java.util.Collection; diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/ChangeManager.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/ChangeManager.java deleted file mode 100644 index 5fa4eeb3b..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/ChangeManager.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions; - -public interface ChangeManager { - - /** - * This method does a quick check to find out whether there are any changes - * to the clone. It does a object value comparison, i. e. it compares each - * value of the clone against the original value and returns true if a - * change is found. - * - * @param original The original object. - * @param clone The clone, whose changes we are looking for. - * @return True if there is a change (at least one) or false, if the values are identical. - */ - boolean hasChanges(Object original, Object clone); - - /** - * Calculates the changes that happened to the clone object. If there are no - * changes, null is returned. The changes are written into the change set - * passed in as argument. - * - * @param changeSet Contains references to the original and clone objects. Into this change set the changes should - * be propagated - * @return {@code true} if there were any changes, {@code false} otherwise - * @throws NullPointerException If {@code changeSet} is {@code null} - */ - boolean calculateChanges(ObjectChangeSet changeSet); - -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/ChangeRecord.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/ChangeRecord.java deleted file mode 100644 index d55699358..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/ChangeRecord.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions; - -import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; - -/** - * Objects of classes implementing this interface represent a change of one attribute of an entity class. Objects store - * only the new value, old value is in the original and it is not needed. - */ -public interface ChangeRecord { - - /** - * Returns the new value of the attribute. - * - * @return Object - */ - Object getNewValue(); - - /** - * Sets the new value of the attribute in case this change record needs to be updated. - * - * @param value The value to set - */ - void setNewValue(Object value); - - /** - * Gets the attribute to which this change record is bound. - * - * @return the attribute - */ - FieldSpecification getAttribute(); - - /** - * Whether this change record prevents caching of the instance on which the change is applied. - * - * @return Whether this change record prevents caching - */ - boolean doesPreventCaching(); - - /** - * Marks this change record to prevent caching. - * - * @see #doesPreventCaching() - */ - void preventCaching(); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CloneBuilder.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CloneBuilder.java deleted file mode 100644 index 074da61ff..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CloneBuilder.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions; - -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; - -import java.lang.reflect.Field; - -/** - * Objects of this interface are responsible for building clones for UnitOfWork - * transactions. - */ -public interface CloneBuilder { - - /** - * Builds clone of the given object. - * - * @param original Object - * @param cloneConfiguration Configuration for the cloning process - * @return Object The clone - * @throws NullPointerException If {@code original} or {@code repository} is {@code null} - */ - Object buildClone(Object original, CloneConfiguration cloneConfiguration); - - /** - * Builds clone of the given object. - *

- * This method differs from {@link #buildClone(Object, CloneConfiguration)} in that it - * accepts another argument which represents the owner of the built clone. - * This is useful in situations when we are cloning attributes directly, e. g. when lazily loading a field value. - * - * @param cloneOwner The owner of the created clone - * @param clonedField The field whose value is being cloned - * @param original The original to clone - * @param descriptor Entity descriptor - * @return The clone - * @throws NullPointerException If {@code cloneOwner}, {@code original} or {@code contextUri} is {@code null} - */ - Object buildClone(Object cloneOwner, Field clonedField, Object original, Descriptor descriptor); - - /** - * Resets the clone builder. - *

- * Especially resets the visited objects cache to make sure all the clones are built from scratch and are not - * affected by the previously built ones. - */ - void reset(); - - /** - * Removes the specified instance from the clone builder's visited entities cache. - * - * @param instance The instance to remove (original object). - * @param descriptor Instance descriptor - */ - void removeVisited(Object instance, Descriptor descriptor); - - /** - * Merges the changes on clone into the original object. - * - * @param changeSet Contains changes to merge - */ - void mergeChanges(ObjectChangeSet changeSet); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/MergeManager.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/MergeManager.java deleted file mode 100644 index cf13f4c62..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/MergeManager.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions; - -/** - * This interface defines methods for merging changes from clones to originals. - */ -public interface MergeManager { - - /** - * Merge changes from one ObjectChangeSet, which represents the changes made - * to clone, into the original object. - * - * @param changeSet ObjectChangeSet - * @return Object The merged object. - */ - Object mergeChangesOnObject(ObjectChangeSet changeSet); - - /** - * Merge changes from the provided UnitOfWorkChangeSet into the target - * session. - * - * @param changeSet UnitOfWorkChangeSet - */ - void mergeChangesFromChangeSet(UnitOfWorkChangeSet changeSet); - - /** - * Merge newly created object into the shared session cache. - * - * @param changeSet ObjectChangeSet - */ - void mergeNewObject(ObjectChangeSet changeSet); - -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/ObjectChangeSet.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/ObjectChangeSet.java deleted file mode 100644 index b5669fb8c..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/ObjectChangeSet.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions; - -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; - -import java.net.URI; -import java.util.Set; - -public interface ObjectChangeSet { - - /** - * Adds a new change record to this change set. - *

- * If there was a change for attribute represented by the new record, it will be overwritten. - * - * @param record The record to add - */ - void addChangeRecord(ChangeRecord record); - - /** - * Gets type of the changed object. - * - * @return Object type - */ - Class getObjectClass(); - - /** - * Gets changes held in this change set. - * - * @return Set of changes - */ - Set getChanges(); - - /** - * Whether this change set contains an changes. - * - * @return {@code true} if there are any changes in this change set, {@code false} otherwise - */ - boolean hasChanges(); - - /** - * Specifies whether this change set represents a new object. - * - * @param isNew Whether this is a new object's change set - */ - void setNew(boolean isNew); - - /** - * Whether this is a new object's change set. - * - * @return Whether target object is new - */ - boolean isNew(); - - /** - * Gets the clone with changes. - * - * @return Clone - */ - Object getCloneObject(); - - /** - * Gets the original object. - * - * @return Original - */ - Object getChangedObject(); - - /** - * Gets descriptor of the changed object. - * - * @return Instance descriptor - */ - Descriptor getEntityDescriptor(); - - /** - * Gets ontology context URI, to which the changed object belongs. - * - * @return context URI - */ - URI getEntityContext(); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/QueryFactory.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/QueryFactory.java deleted file mode 100644 index 0bae39b9f..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/QueryFactory.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions; - -import cz.cvut.kbss.jopa.model.query.Query; -import cz.cvut.kbss.jopa.model.query.TypedQuery; - -public interface QueryFactory { - - /** - * Creates query object representing a native SPARQL query. - * - * @param sparql The query - * @return Query object - * @throws NullPointerException If {@code sparql} is {@code null} - */ - Query createNativeQuery(String sparql); - - /** - * Creates typed query object representing a native SPARQL query. - * - * @param sparql The query - * @param resultClass Type of the results - * @return Query object - * @throws NullPointerException If {@code sparql} or {@code resultClass} is {@code null} - */ - TypedQuery createNativeQuery(String sparql, Class resultClass); - - /** - * Creates a query object representing a native SPARQL query. - * @param sparql The query - * @param resultSetMapping Name of the result set mapping to apply - * @return Query object - * * @throws NullPointerException If {@code sparql} or {@code resultSetMapping} is {@code null} - */ - Query createNativeQuery(String sparql, String resultSetMapping); - - /** - * Creates query object representing a native SPARQL query. - * - * @param query The query - * @return Query object - * @throws NullPointerException If {@code sparql} is {@code null} - */ - Query createQuery(String query); - - /** - * Creates typed query object representing a native SPARQL query. - * - * @param query The query - * @param resultClass Type of the results param URI of the ontology context against which the query will be - * evaluated - * @return Query object - * @throws NullPointerException If {@code sparql} or {@code resultClass} is {@code null} - */ - TypedQuery createQuery(String query, Class resultClass); - - /** - * Creates a query object representing a native SPARQL query. - * - * @param name The name of the query defined in metadata - * @return Query object - * @throws IllegalArgumentException If a query has not been defined with the given name - */ - Query createNamedQuery(String name); - - /** - * Creates a typed query object representing a native SPARQL query. - * - * @param name The name of the query defined in metadata - * @param resultClass Type of the results param URI of the ontology context against which the query will be - * evaluated - * @return Query object - * @throws IllegalArgumentException If a query has not been defined with the given name - */ - TypedQuery createNamedQuery(String name, Class resultClass); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/Session.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/Session.java deleted file mode 100644 index e918c9471..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/Session.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions; - -import java.net.URI; - -public interface Session { - - /** - * Acquires UnitOfWork object to perform transaction operations. - * - * @return UnitOfWork - */ - UnitOfWork acquireUnitOfWork(); - - /** - * Release this session and all its children. - */ - void release(); - - /** - * Remove the given object from the session's live object cache. This is - * particularly meant for merging deleted objects from transactions. - * - * @param object - * Object - * @param context - * Entity context URI - */ - void removeObjectFromCache(Object object, URI context); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/transactions/TransactionWrapper.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/transactions/TransactionWrapper.java deleted file mode 100644 index 6f56e9436..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/transactions/TransactionWrapper.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.transactions; - -/** - * This interface enables JTA transactions and EntityTransactions to be handled uniformly. - */ -@FunctionalInterface -public interface TransactionWrapper { - - EntityTransaction getTransaction(); -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/utils/ErrorUtils.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/utils/ErrorUtils.java deleted file mode 100644 index ad11fc234..000000000 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/utils/ErrorUtils.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.utils; - -import java.util.function.Supplier; - -public final class ErrorUtils { - - /** - * Error message stating that an argument cannot be null.

- *

- * This message contains a placeholder {@code arg}, which should be replaced - * with the actual argument name. - */ - private static final String ARGUMENT_NULL = "Argument '$arg$' cannot be null."; - - private static final String PLACEHOLDER = "$arg$"; - - private ErrorUtils() { - throw new AssertionError(); - } - - /** - * Provides a {@link NullPointerException} message supplier. - *

- * This supplier constructs messages which state than an argument with the specified name cannot be null. - * - * @param argName Argument name - * @return Error message supplier - */ - public static Supplier getNPXMessageSupplier(final String argName) { - return () -> ARGUMENT_NULL.replace(PLACEHOLDER, argName); - } -} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/vocabulary/SKOS.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/vocabulary/SKOS.java index 708397772..df6295019 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/vocabulary/SKOS.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/vocabulary/SKOS.java @@ -29,6 +29,11 @@ public final class SKOS { */ public static final String NAMESPACE = "http://www.w3.org/2004/02/skos/core#"; + /** + * Typical prefix used for {@link #NAMESPACE}. + */ + public static final String PREFIX = "skos"; + /** * SKOS Collection class. * diff --git a/jopa-api/src/test/java/cz/cvut/kbss/jopa/model/metamodel/CollectionTypeTest.java b/jopa-api/src/test/java/cz/cvut/kbss/jopa/model/metamodel/CollectionTypeTest.java index 783e8360b..934f9eb49 100644 --- a/jopa-api/src/test/java/cz/cvut/kbss/jopa/model/metamodel/CollectionTypeTest.java +++ b/jopa-api/src/test/java/cz/cvut/kbss/jopa/model/metamodel/CollectionTypeTest.java @@ -53,4 +53,4 @@ static Stream fromClassTestValues() { void fromClassThrowsIllegalArgumentExceptionForUnsupportedClass() { assertThrows(IllegalArgumentException.class, () -> CollectionType.fromClass(String.class)); } -} \ No newline at end of file +} diff --git a/jopa-distribution/pom.xml b/jopa-distribution/pom.xml index c89fb89ca..511994397 100644 --- a/jopa-distribution/pom.xml +++ b/jopa-distribution/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml diff --git a/jopa-impl/pom.xml b/jopa-impl/pom.xml index e42f31954..df77d5467 100644 --- a/jopa-impl/pom.xml +++ b/jopa-impl/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml @@ -15,7 +15,8 @@ jar - 4.9.3 + 4.12.0 + 1.14.8 @@ -34,28 +35,16 @@ datatype ${project.version} - - org.aspectj - aspectjrt - ${org.aspectj.version} - - - org.aspectj - aspectjweaver - ${org.aspectj.version} - runtime - - - org.jgrapht - jgrapht-core - 1.1.0 - runtime - org.antlr antlr4-runtime ${org.antlr4.version} + + net.bytebuddy + byte-buddy + ${net.bytebuddy.version} + @@ -74,49 +63,6 @@ - - dev.aspectj - aspectj-maven-plugin - - ${jdk.version} - ${jdk.version} - ${jdk.version} - - - ${basedir}/src/main/java - - **/BeanListenerAspect.java - - - - - - - org.aspectj - aspectjtools - ${org.aspectj.version} - - - - - compile - process-classes - - compile - - - - - - test-compile - process-test-classes - - - test-compile - - - - org.apache.maven.plugins maven-surefire-plugin diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/accessors/DefaultStorageAccessor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/accessors/DefaultStorageAccessor.java index 6d43c1075..2ad48b4ef 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/accessors/DefaultStorageAccessor.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/accessors/DefaultStorageAccessor.java @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.accessors; +import cz.cvut.kbss.jopa.exception.DataSourceCreationException; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; import cz.cvut.kbss.jopa.utils.ReflectionUtils; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/AbstractTypeException.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/AbstractTypeException.java new file mode 100644 index 000000000..43fe4de73 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/AbstractTypeException.java @@ -0,0 +1,17 @@ +package cz.cvut.kbss.jopa.exception; + +import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; + +/** + * Indicates that an attempt has been made to instantiate a class that is abstract in terms of JOPA. + *

+ * This could be, for example, a {@link cz.cvut.kbss.jopa.model.annotations.MappedSuperclass} type, an {@link + * cz.cvut.kbss.jopa.model.metamodel.EntityType} representing an interface or an abstract entity class that needs to be + * inherited for instantiation. + */ +public class AbstractTypeException extends OWLPersistenceException { + + public AbstractTypeException(String message) { + super(message); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/accessors/DataSourceCreationException.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/DataSourceCreationException.java similarity index 81% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/accessors/DataSourceCreationException.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/DataSourceCreationException.java index c4ec46249..3e00dcced 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/accessors/DataSourceCreationException.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/DataSourceCreationException.java @@ -15,22 +15,14 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.accessors; +package cz.cvut.kbss.jopa.exception; /** * Indicates that a data source cannot be created. */ public class DataSourceCreationException extends RuntimeException { - public DataSourceCreationException(String message) { - super(message); - } - public DataSourceCreationException(String message, Throwable cause) { super(message, cause); } - - public DataSourceCreationException(Throwable cause) { - super(cause); - } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/LazyLoadingException.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/LazyLoadingException.java new file mode 100644 index 000000000..e6f9f7bbe --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/LazyLoadingException.java @@ -0,0 +1,20 @@ +package cz.cvut.kbss.jopa.exception; + +import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; + +/** + * Indicates an issue with lazy loading. + *

+ * Typically, this would be an attempt to trigger lazy loading on a proxy that is not attached to any active persistence + * context. + */ +public class LazyLoadingException extends OWLPersistenceException { + + public LazyLoadingException(String message) { + super(message); + } + + public LazyLoadingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/DefaultClasspathScanner.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/DefaultClasspathScanner.java index 4c8790f87..f0be858e2 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/DefaultClasspathScanner.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/DefaultClasspathScanner.java @@ -23,9 +23,7 @@ import java.io.File; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -56,7 +54,7 @@ public class DefaultClasspathScanner implements ClasspathScanner { protected final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); protected String pathPattern; - protected Set visited; + protected Set visited; @Override public void addListener(Consumer> listener) { @@ -64,7 +62,8 @@ public void addListener(Consumer> listener) { } /** - * Inspired by https://github.com/ddopson/java-class-enumerator + * Inspired by https://github.com/ddopson/java-class-enumerator */ @Override public void processClasses(String scanPackage) { @@ -84,15 +83,16 @@ public void processClasses(String scanPackage) { protected void processElements(Enumeration urls, String scanPath) throws IOException { while (urls.hasMoreElements()) { final URL url = urls.nextElement(); - if (visited.contains(url)) { + final String elemUri = url.toString(); + if (visited.contains(elemUri)) { continue; } - visited.add(url); + visited.add(elemUri); LOG.trace("Processing classpath element {}", url); - if (isJar(url.toString())) { + if (isJar(elemUri)) { processJarFile(createJarFile(url)); } else { - processDirectory(new File(getUrlAsUri(url).getPath()), scanPath); + processDirectory(new File(URI.create(elemUri).getPath()), scanPath); } } } @@ -102,9 +102,8 @@ protected void processElements(Enumeration urls, String scanPath) throws IO * * @param url Resource URL (presumably leading to a local file) * @return Decoded argument - * @throws UnsupportedEncodingException Should not happen, using standard UTF-8 encoding */ - protected static String sanitizePath(URL url) throws UnsupportedEncodingException { + protected static String sanitizePath(URL url) { return URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8); } @@ -119,16 +118,6 @@ protected static JarFile createJarFile(URL elementUrl) throws IOException { return new JarFile(jarPath); } - protected static URI getUrlAsUri(URL url) { - try { - // Transformation to URI handles encoding, e.g. of whitespaces in the path - return url.toURI(); - } catch (URISyntaxException ex) { - throw new OWLPersistenceException( - "Unable to scan resource " + url + ". It is not a valid URI.", ex); - } - } - /** * Processes the specified {@link JarFile}, looking for classes in the configured package. * @@ -165,7 +154,7 @@ protected void processClass(String className) { listeners.forEach(listener -> listener.accept(cls)); } catch (Exception | NoClassDefFoundError e) { LOG.debug("Unable to load class {}, got error {}: {}. Skipping the class. If it is an entity class, ensure it is available on classpath and is built with supported Java version.", className, e.getClass() - .getName(), e.getMessage()); + .getName(), e.getMessage()); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/PersistenceUnitClassFinder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/PersistenceUnitClassFinder.java index 51e5c76b5..c190e3036 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/PersistenceUnitClassFinder.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/PersistenceUnitClassFinder.java @@ -47,12 +47,14 @@ public class PersistenceUnitClassFinder { * relevant for the persistence provider. *

* These classes include: - *

  • Entities, i.e. classes annotated with {@link cz.cvut.kbss.jopa.model.annotations.OWLClass},
  • - *
  • Result result mapping classes, i.e. classes annotated with {@link cz.cvut.kbss.jopa.model.annotations.SparqlResultSetMapping} - * or {@link cz.cvut.kbss.jopa.model.annotations.SparqlResultSetMappings}
+ *
    + *
  • Entities, i.e. classes annotated with {@link cz.cvut.kbss.jopa.model.annotations.OWLClass},
  • + *
  • Result result mapping classes, i.e. classes annotated with {@link cz.cvut.kbss.jopa.model.annotations.SparqlResultSetMapping} + * or {@link cz.cvut.kbss.jopa.model.annotations.SparqlResultSetMappings}
  • + *
* - * @param configuration Persistence configuration, should contain value for the {@link - * JOPAPersistenceProperties#SCAN_PACKAGE} property + * @param configuration Persistence configuration, should contain value for the + * {@link JOPAPersistenceProperties#SCAN_PACKAGE} property * @throws IllegalArgumentException If {@link JOPAPersistenceProperties#SCAN_PACKAGE} values is missing */ public void scanClasspath(Configuration configuration) { @@ -70,7 +72,7 @@ public void scanClasspath(Configuration configuration) { private static ClasspathScanner resolveClasspathScanner(Configuration config) { try { final String scannerType = config.get(JOPAPersistenceProperties.CLASSPATH_SCANNER_CLASS, - DefaultClasspathScanner.class.getName()); + DefaultClasspathScanner.class.getName()); final Class scannerCls = Class.forName(scannerType); return (ClasspathScanner) ReflectionUtils.instantiateUsingDefaultConstructor(scannerCls); } catch (ClassNotFoundException | cz.cvut.kbss.jopa.exception.InstantiationException e) { @@ -100,9 +102,10 @@ public Set getResultSetMappings() { /** * Gets {@link cz.cvut.kbss.jopa.model.AttributeConverter} implementations found during classpath scanning. - * + *

* The converters are wrapped in an internal helper class {@link ConverterWrapper}. - * @return Set of custom converters + * + * @return Map of classes to custom converters */ public Map, ConverterWrapper> getAttributeConverters() { assert scanned; 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 f5db7b42c..2dd0629f2 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,7 +24,6 @@ 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.ErrorUtils; import cz.cvut.kbss.jopa.utils.Procedure; import cz.cvut.kbss.jopa.utils.ThrowingConsumer; import cz.cvut.kbss.ontodriver.ResultSet; @@ -34,7 +33,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -56,8 +60,8 @@ abstract class AbstractQuery implements Query { }; AbstractQuery(QueryHolder query, ConnectionWrapper connection) { - this.query = Objects.requireNonNull(query, ErrorUtils.getNPXMessageSupplier("query")); - this.connection = Objects.requireNonNull(connection, ErrorUtils.getNPXMessageSupplier("connection")); + this.query = Objects.requireNonNull(query); + this.connection = Objects.requireNonNull(connection); } private void logQuery() { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/BeanListenerAspect.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/BeanListenerAspect.java deleted file mode 100644 index 9923094fb..000000000 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/BeanListenerAspect.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.model; - -import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; -import cz.cvut.kbss.jopa.model.metamodel.EntityType; -import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; -import cz.cvut.kbss.jopa.sessions.validator.AttributeModificationValidator; -import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.*; -import org.aspectj.lang.reflect.FieldSignature; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Serializable; -import java.lang.reflect.Field; - -@Aspect -public class BeanListenerAspect { - - private static final Logger LOG = LoggerFactory.getLogger(BeanListenerAspect.class); - - public interface Manageable { - void setPersistenceContext(UnitOfWorkImpl uow); - - UnitOfWorkImpl getPersistenceContext(); - } - - public static class ManageableImpl implements Manageable, Serializable { - private transient UnitOfWorkImpl persistenceContext; - - @Override - public void setPersistenceContext(UnitOfWorkImpl uow) { - this.persistenceContext = uow; - } - - @Override - public UnitOfWorkImpl getPersistenceContext() { - return persistenceContext; - } - } - - @DeclareMixin(value = "!is(InterfaceType) && (@cz.cvut.kbss.jopa.model.annotations.OWLClass *) && (!@cz.cvut.kbss.jopa.model.annotations.util.NonEntity *)") - public static Manageable createImpl() { - return new ManageableImpl(); - } - - @Pointcut("get( @(cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty " + - "|| cz.cvut.kbss.jopa.model.annotations.OWLDataProperty " + - "|| cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty " + - "|| cz.cvut.kbss.jopa.model.annotations.Types " + - "|| cz.cvut.kbss.jopa.model.annotations.Properties " + - "|| cz.cvut.kbss.jopa.model.annotations.Sparql) * * ) " + - "&& ((within(@cz.cvut.kbss.jopa.model.annotations.OWLClass *) && !within(@cz.cvut.kbss.jopa.model.annotations.util.NonEntity *)) " + - "|| within(@cz.cvut.kbss.jopa.model.annotations.MappedSuperclass *))") - void getter() { - } - - @Pointcut("set( @(cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty " + - "|| cz.cvut.kbss.jopa.model.annotations.OWLDataProperty " + - "|| cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty " + - "|| cz.cvut.kbss.jopa.model.annotations.Types || cz.cvut.kbss.jopa.model.annotations.Properties ) * * ) " + - "&& ((within(@cz.cvut.kbss.jopa.model.annotations.OWLClass *) && !within(@cz.cvut.kbss.jopa.model.annotations.util.NonEntity *)) " + - "|| within(@cz.cvut.kbss.jopa.model.annotations.MappedSuperclass *))") - void setter() { - } - - /** - * Ties the specified instance to its persistence context, so that the advices can identify it easily. - * - * @param instance The managed instance - * @param persistenceContext Persistence context which is managing it - */ - public void register(Object instance, UnitOfWorkImpl persistenceContext) { - ((Manageable) instance).setPersistenceContext(persistenceContext); - } - - /** - * Disconnects the specified instance from its persistence context. - * - * @param instance The instance to remove - * @see #register(Object, UnitOfWorkImpl) - */ - public void deregister(Object instance) { - ((Manageable) instance).setPersistenceContext(null); - } - - @AfterReturning("setter()") - public void afterSetter(JoinPoint thisJoinPoint) { - // Persist changes done during transaction and check for inferred attribute modification - final Object entity = thisJoinPoint.getTarget(); - if (!(entity instanceof Manageable)) { - return; - } - final UnitOfWorkImpl persistenceContext = ((Manageable) entity).getPersistenceContext(); - if (persistenceContext == null || !persistenceContext.isInTransaction()) { - return; - } - - try { - final Field field = ((FieldSignature) thisJoinPoint.getSignature()).getField(); - if (EntityPropertiesUtils.isFieldTransient(field)) { - return; - } - final FieldSpecification fieldSpec = getFieldSpecification(entity, - thisJoinPoint.getSignature().getName(), - persistenceContext); - AttributeModificationValidator.verifyCanModify(fieldSpec); - persistenceContext.attributeChanged(entity, field); - } catch (SecurityException e) { - LOG.error(e.getMessage(), e); - throw new OWLPersistenceException(e.getMessage()); - } - } - - private static FieldSpecification getFieldSpecification(Object entity, String fieldName, - UnitOfWorkImpl persistenceContext) { - final EntityType et = persistenceContext.getMetamodel().entity(entity.getClass()); - assert et != null; - return et.getFieldSpecification(fieldName); - } - - @Before("getter()") - public void beforeGetter(JoinPoint thisJoinPoint) { - // Load lazy loaded entity field - final Object entity = thisJoinPoint.getTarget(); - if (!(entity instanceof Manageable)) { - return; - } - final UnitOfWorkImpl persistenceContext = ((Manageable) entity).getPersistenceContext(); - if (persistenceContext == null || !persistenceContext.contains(entity)) { - return; - } - final Field field = ((FieldSignature) thisJoinPoint.getSignature()).getField(); - if (EntityPropertiesUtils.isFieldTransient(field)) { - return; - } - persistenceContext.loadEntityField(entity, field); - } -} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/CriteriaQueryImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/CriteriaQueryImpl.java index eaea513e8..a34c0631f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/CriteriaQueryImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/CriteriaQueryImpl.java @@ -19,14 +19,25 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.Metamodel; -import cz.cvut.kbss.jopa.model.query.criteria.*; -import cz.cvut.kbss.jopa.query.criteria.*; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaQuery; +import cz.cvut.kbss.jopa.model.query.criteria.Expression; +import cz.cvut.kbss.jopa.model.query.criteria.Order; +import cz.cvut.kbss.jopa.model.query.criteria.Predicate; +import cz.cvut.kbss.jopa.model.query.criteria.Root; +import cz.cvut.kbss.jopa.model.query.criteria.Selection; +import cz.cvut.kbss.jopa.query.criteria.AbstractPredicate; +import cz.cvut.kbss.jopa.query.criteria.CriteriaBuilderImpl; +import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; +import cz.cvut.kbss.jopa.query.criteria.CriteriaQueryHolder; +import cz.cvut.kbss.jopa.query.criteria.RootImpl; import cz.cvut.kbss.jopa.query.criteria.expressions.AbstractExpression; import cz.cvut.kbss.jopa.query.soql.SoqlConstants; -import cz.cvut.kbss.jopa.utils.ErrorUtils; - -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; public class CriteriaQueryImpl implements CriteriaQuery { @@ -36,7 +47,7 @@ public class CriteriaQueryImpl implements CriteriaQuery { public CriteriaQueryImpl(CriteriaQueryHolder query, Metamodel metamodel, CriteriaBuilderImpl cb) { - this.query = Objects.requireNonNull(query, ErrorUtils.getNPXMessageSupplier("query")); + this.query = Objects.requireNonNull(query); this.metamodel = metamodel; this.cb = cb; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImpl.java index ab0e28dd0..eb45c84aa 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImpl.java @@ -21,7 +21,7 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.Metamodel; import cz.cvut.kbss.jopa.model.query.Query; -import cz.cvut.kbss.jopa.sessions.Cache; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import cz.cvut.kbss.jopa.sessions.ServerSession; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; @@ -126,6 +126,13 @@ private synchronized void initServerSession() { } } + @Override + public CriteriaBuilder getCriteriaBuilder() { + ensureOpen(); + initServerSession(); + return serverSession.getCriteriaBuilder(); + } + /** * The server session should by initialized by now, but to make sure, there is default initialization with an empty * properties map. @@ -197,7 +204,6 @@ public boolean isLoaded(Object entity, String attributeName) { @Override public boolean isLoaded(Object entity) { Objects.requireNonNull(entity); - // Since we do not support getReference yet, all EAGER attributes are always loaded for managed instances return em.stream().anyMatch(emi -> emi.isLoaded(entity)); } 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 719c27e43..ceb0aa1ab 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 @@ -26,20 +26,27 @@ import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.Metamodel; import cz.cvut.kbss.jopa.model.query.TypedQuery; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import cz.cvut.kbss.jopa.model.query.criteria.CriteriaQuery; import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; import cz.cvut.kbss.jopa.sessions.ServerSession; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.jopa.transactions.EntityTransaction; import cz.cvut.kbss.jopa.transactions.EntityTransactionWrapper; -import cz.cvut.kbss.jopa.transactions.TransactionWrapper; -import cz.cvut.kbss.jopa.utils.*; +import cz.cvut.kbss.jopa.utils.CollectionFactory; +import cz.cvut.kbss.jopa.utils.Configuration; +import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; +import cz.cvut.kbss.jopa.utils.Wrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; public class EntityManagerImpl implements AbstractEntityManager, Wrapper { @@ -52,8 +59,8 @@ public class EntityManagerImpl implements AbstractEntityManager, Wrapper { private boolean open; - private TransactionWrapper transaction; - private UnitOfWorkImpl persistenceContext; + private final EntityTransactionWrapper transaction; + private UnitOfWork persistenceContext; private final Configuration configuration; private Map cascadingRegistry = new IdentityHashMap<>(); @@ -63,15 +70,11 @@ public class EntityManagerImpl implements AbstractEntityManager, Wrapper { this.serverSession = serverSession; this.configuration = configuration; - setTransactionWrapper(); + this.transaction = new EntityTransactionWrapper(this); this.open = true; } - public enum State { - MANAGED, MANAGED_NEW, NOT_MANAGED, REMOVED - } - @Override public void persist(final Object entity) { final Descriptor d = new EntityDescriptor(); @@ -82,8 +85,8 @@ public void persist(final Object entity) { public void persist(final Object entity, final Descriptor descriptor) { LOG.trace("Persisting instance of type {}.", entity.getClass()); try { - Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity")); - Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor")); + Objects.requireNonNull(entity); + Objects.requireNonNull(descriptor); ensureOpen(); checkClassIsValidEntity(entity.getClass()); @@ -165,8 +168,8 @@ public T merge(final T entity) { @Override public T merge(final T entity, final Descriptor descriptor) { try { - Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity")); - Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor")); + Objects.requireNonNull(entity); + Objects.requireNonNull(descriptor); ensureOpen(); checkClassIsValidEntity(entity.getClass()); @@ -280,9 +283,9 @@ public T find(Class cls, Object identifier) { @Override public T find(Class cls, Object identifier, Descriptor descriptor) { try { - Objects.requireNonNull(cls, ErrorUtils.getNPXMessageSupplier("cls")); - Objects.requireNonNull(identifier, ErrorUtils.getNPXMessageSupplier("primaryKey")); - Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor")); + Objects.requireNonNull(cls); + Objects.requireNonNull(identifier); + Objects.requireNonNull(descriptor); ensureOpen(); checkClassIsValidEntity(cls); @@ -543,7 +546,7 @@ public boolean isInferred(T entity, FieldSpecification attribu @Override public CriteriaBuilder getCriteriaBuilder() { - return getCurrentPersistenceContext().criteriaFactory(); + return getCurrentPersistenceContext().getCriteriaBuilder(); } @Override @@ -566,11 +569,11 @@ private void ensureOpen() { } } - private State getState(Object entity) { + private EntityState getState(Object entity) { return getCurrentPersistenceContext().getState(entity); } - private State getState(Object entity, Descriptor descriptor) { + private EntityState getState(Object entity, Descriptor descriptor) { return getCurrentPersistenceContext().getState(entity, descriptor); } @@ -583,12 +586,11 @@ protected void finalize() throws Throwable { } @Override - public UnitOfWorkImpl getCurrentPersistenceContext() { - if (this.persistenceContext == null) { - this.persistenceContext = (UnitOfWorkImpl) serverSession.acquireUnitOfWork(); - persistenceContext.setEntityManager(this); + public UnitOfWork getCurrentPersistenceContext() { + if (persistenceContext == null) { + this.persistenceContext = serverSession.acquireUnitOfWork(configuration); } - return this.persistenceContext; + return persistenceContext; } /** @@ -612,16 +614,6 @@ public void transactionFinished(EntityTransaction t) { this.serverSession.transactionFinished(t); } - /** - * Since we support only EntityTransactions, we set the TransactionWrapper to EntityTransactionWrapper. - *

- * In the future, if JTA transactions are supported, JTATransactionWrapper should be set instead of the - * EntityTransactionWrapper. - */ - private void setTransactionWrapper() { - this.transaction = new EntityTransactionWrapper(this); - } - @Override public Configuration getConfiguration() { return configuration; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityState.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityState.java new file mode 100644 index 000000000..2b4f0d378 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityState.java @@ -0,0 +1,8 @@ +package cz.cvut.kbss.jopa.model; + +/** + * Represents the state of an entity with respect to a persistence context. + */ +public enum EntityState { + MANAGED, MANAGED_NEW, NOT_MANAGED, REMOVED +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/JOPAPersistenceProperties.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/JOPAPersistenceProperties.java index 339797a16..baca9c82d 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/JOPAPersistenceProperties.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/JOPAPersistenceProperties.java @@ -91,16 +91,29 @@ public final class JOPAPersistenceProperties extends PersistenceProperties { */ public static final String CLASSPATH_SCANNER_CLASS = "cz.cvut.jopa.classpathScanner"; + /** + * Path to a directory to which classes dynamically generated by JOPA should be output. + *

+ * If not configured, generated classes are not output anywhere. + */ + public static final String CLASS_GENERATOR_OUTPUT_DIR = "cz.cvut.jopa.classGeneratorOutputDir"; + /** * Ignores removal of inferred values when entity state is merged into the persistence context. *

- * Normally, attempts to remove inferred values lead to an {@link cz.cvut.kbss.jopa.exceptions.InferredAttributeModifiedException}. - * But when an entity is merged into the persistence context, it may be merged without inferred attribute values - * because they were not accessible to the client application. Instead of requiring the application to reconstruct - * them, this configuration allows to ignore such changes completely. + * Normally, attempts to remove inferred values lead to an + * {@link cz.cvut.kbss.jopa.exceptions.InferredAttributeModifiedException}. But when an entity is merged into the + * persistence context, it may be merged without inferred attribute values because they were not accessible to the + * client application. Instead of requiring the application to reconstruct them, this configuration allows to ignore + * such changes completely. */ public static final String IGNORE_INFERRED_VALUE_REMOVAL_ON_MERGE = "cz.cvut.kbss.jopa.ignoreInferredValueRemovalOnMerge"; + /** + * Configures change tracking mode. + */ + public static final String CHANGE_TRACKING_MODE = "cz.cvut.kbss.jopa.changeTrackingMode"; + private JOPAPersistenceProperties() { throw new AssertionError(); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/Manageable.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/Manageable.java new file mode 100644 index 000000000..b7ce14879 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/Manageable.java @@ -0,0 +1,25 @@ +package cz.cvut.kbss.jopa.model; + +import cz.cvut.kbss.jopa.sessions.UnitOfWork; + +/** + * Interface dynamically assigned to entity classes so that their instances may be attached to a persistence context. + */ +public interface Manageable { + + /** + * Sets persistence context to this instance. + *

+ * Done when an object enters the persistence context + * + * @param uow Persistence context + */ + void setPersistenceContext(UnitOfWork uow); + + /** + * Gets the persistence context associated with this entity instance. + * + * @return Persistence context instance + */ + UnitOfWork getPersistenceContext(); +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/MetamodelImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/MetamodelImpl.java index 38cb8fe18..42ce341bc 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/MetamodelImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/MetamodelImpl.java @@ -19,17 +19,29 @@ import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; import cz.cvut.kbss.jopa.loaders.PersistenceUnitClassFinder; -import cz.cvut.kbss.jopa.model.metamodel.*; +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.model.metamodel.ManagedType; +import cz.cvut.kbss.jopa.model.metamodel.Metamodel; +import cz.cvut.kbss.jopa.model.metamodel.MetamodelBuilder; +import cz.cvut.kbss.jopa.model.metamodel.StaticMetamodelInitializer; +import cz.cvut.kbss.jopa.proxy.lazy.gen.LazyLoadingEntityProxyGenerator; import cz.cvut.kbss.jopa.query.NamedQueryManager; import cz.cvut.kbss.jopa.query.ResultSetMappingManager; import cz.cvut.kbss.jopa.sessions.MetamodelProvider; import cz.cvut.kbss.jopa.utils.Configuration; +import cz.cvut.kbss.jopa.utils.MetamodelUtils; import cz.cvut.kbss.ontodriver.config.OntoDriverProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -37,13 +49,13 @@ public class MetamodelImpl implements Metamodel, MetamodelProvider { private static final Logger LOG = LoggerFactory.getLogger(Metamodel.class); - private static final String ASPECTJ_CLASS = "org.aspectj.weaver.loadtime.Agent"; - private Map, ManagedType> typeMap; private Map, EntityType> entities; private Set> inferredClasses; private TypeReferenceMap typeReferenceMap; + private final Map, Class> lazyLoadingProxyClasses = new ConcurrentHashMap<>(); + private NamedQueryManager namedQueryManager; private ResultSetMappingManager resultSetMappingManager; @@ -61,15 +73,14 @@ public MetamodelImpl(Configuration configuration) { } /** - * Builds the metamodel for classes (entity classes, attribute converters etc.) discovered by the specified {@link - * PersistenceUnitClassFinder}. + * Builds the metamodel for classes (entity classes, attribute converters etc.) discovered by the specified + * {@link PersistenceUnitClassFinder}. * * @param classFinder Finder of PU classes */ public void build(PersistenceUnitClassFinder classFinder) { Objects.requireNonNull(classFinder); LOG.debug("Building metamodel..."); - checkForWeaver(); classFinder.scanClasspath(configuration); final MetamodelBuilder metamodelBuilder = new MetamodelBuilder(configuration); @@ -84,18 +95,6 @@ public void build(PersistenceUnitClassFinder classFinder) { new StaticMetamodelInitializer(this).initializeStaticMetamodel(); } - /** - * Check the class path for aspectj weaver, which is vital for using lazy loading. - */ - private static void checkForWeaver() { - try { - MetamodelImpl.class.getClassLoader().loadClass(ASPECTJ_CLASS); - } catch (ClassNotFoundException e) { - LOG.error("AspectJ not found on classpath. Cannot run without AspectJ."); - throw new OWLPersistenceException(e); - } - } - /** * Builds a reduced metamodel for the specified set of entity classes. *

@@ -121,10 +120,13 @@ public void build(Set> entityClasses) { @Override public IdentifiableEntityType entity(Class cls) { - if (!isEntityType(cls)) { - throw new IllegalArgumentException(cls.getName() + " is not a known entity in this persistence unit."); + Objects.requireNonNull(cls); + // Unwrap parent from generated subclass if necessary + final Class actualCls = MetamodelUtils.getEntityClass(cls); + if (!isEntityType(actualCls)) { + throw new IllegalArgumentException(actualCls.getName() + " is not a known entity in this persistence unit."); } - return (IdentifiableEntityType) entities.get(cls); + return (IdentifiableEntityType) entities.get(actualCls); } @Override @@ -197,14 +199,14 @@ private void initModuleExtractionSignature() { } @Override - public Metamodel getMetamodel() { + public MetamodelImpl getMetamodel() { return this; } @Override public boolean isEntityType(Class cls) { Objects.requireNonNull(cls); - return entities.containsKey(cls); + return entities.containsKey(MetamodelUtils.getEntityClass(cls)); } /** @@ -216,4 +218,16 @@ public boolean isEntityType(Class cls) { public Set> getReferringTypes(Class cls) { return typeReferenceMap.getReferringTypes(cls); } + + /** + * Gets a {@link cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy} type for the specified class. + * + * @param cls Class to get lazy loading proxy for + * @param Type to proxy + * @return Lazy loading proxy class + */ + public Class getLazyLoadingProxy(Class cls) { + assert isEntityType(cls); + return (Class) lazyLoadingProxyClasses.computeIfAbsent(cls, c -> new LazyLoadingEntityProxyGenerator().generate(c)); + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/ResultSetMappingQuery.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/ResultSetMappingQuery.java index ac109424e..a4fd49d8e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/ResultSetMappingQuery.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/ResultSetMappingQuery.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.jopa.query.QueryHolder; import cz.cvut.kbss.jopa.query.mapper.SparqlResultMapper; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.ontodriver.iteration.ResultRow; /** @@ -29,10 +29,10 @@ public class ResultSetMappingQuery extends QueryImpl { private final SparqlResultMapper mapper; - private final UnitOfWorkImpl uow; + private final UnitOfWork uow; public ResultSetMappingQuery(QueryHolder query, ConnectionWrapper connection, SparqlResultMapper mapper, - UnitOfWorkImpl uow) { + UnitOfWork uow) { super(query, connection); this.mapper = mapper; this.uow = uow; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/SimpleOneLevelCascadeExplorer.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/SimpleOneLevelCascadeExplorer.java index d463ce0e2..3d8fe9294 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/SimpleOneLevelCascadeExplorer.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/SimpleOneLevelCascadeExplorer.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.model; import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import java.util.Collection; @@ -34,7 +35,7 @@ protected SimpleOneLevelCascadeExplorer(Consumer cascadedOperation) { protected void runForEach(final Attribute at, final Object o, boolean cascaded) { Object attVal = EntityPropertiesUtils.getAttributeValue(at, o); - if (attVal == null) { + if (attVal == null || attVal instanceof LazyLoadingProxy) { return; } if (at.isCollection()) { 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 19158a182..3d91d1b01 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 @@ -26,9 +26,7 @@ import cz.cvut.kbss.jopa.model.query.TypedQuery; import cz.cvut.kbss.jopa.query.QueryHolder; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; -import cz.cvut.kbss.jopa.sessions.MetamodelProvider; import cz.cvut.kbss.jopa.sessions.UnitOfWork; -import cz.cvut.kbss.jopa.utils.ErrorUtils; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.iteration.ResultRow; @@ -42,22 +40,16 @@ public class TypedQueryImpl extends AbstractQuery implements TypedQuery { private final Class resultType; - private final MetamodelProvider metamodelProvider; - private UnitOfWork uow; + private final UnitOfWork uow; private Descriptor descriptor = new EntityDescriptor(); public TypedQueryImpl(final QueryHolder query, final Class resultType, - final ConnectionWrapper connection, MetamodelProvider metamodelProvider) { + final ConnectionWrapper connection, UnitOfWork uow) { super(query, connection); - this.resultType = Objects.requireNonNull(resultType, ErrorUtils.getNPXMessageSupplier("resultType")); - this.metamodelProvider = Objects - .requireNonNull(metamodelProvider, ErrorUtils.getNPXMessageSupplier("metamodelProvider")); - } - - public void setUnitOfWork(UnitOfWork uow) { - this.uow = uow; + this.resultType = Objects.requireNonNull(resultType); + this.uow = Objects.requireNonNull(uow); } @Override @@ -75,7 +67,7 @@ public List getResultList() { } private List getResultListImpl() throws OntoDriverException { - final boolean isEntityType = metamodelProvider.isEntityType(resultType); + final boolean isEntityType = uow.isEntityType(resultType); final List res = new ArrayList<>(); executeQuery(rs -> { if (isEntityType) { @@ -137,7 +129,7 @@ public X getSingleResult() { @Override public Stream getResultStream() { - final boolean isEntityType = metamodelProvider.isEntityType(resultType); + final boolean isEntityType = uow.isEntityType(resultType); try { return executeQueryForStream(row -> { if (isEntityType) { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractAttribute.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractAttribute.java index 55b4018bd..00ee3b977 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractAttribute.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractAttribute.java @@ -113,7 +113,7 @@ public ManagedType getDeclaringType() { @Override public Field getJavaField() { - return propertyInfo.getField(); + return propertyInfo.field(); } @Override diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractEntityType.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractEntityType.java index 5cda03e50..12d7a45d2 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractEntityType.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractEntityType.java @@ -20,18 +20,20 @@ import cz.cvut.kbss.jopa.model.IRI; /** - * Instances of the type AbstractEntityType represent entity - * types which are abstract - represented entity type cannot be instantiated. - * Instead, some child class represented by {@link ConcreteEntityType} will be instantiated when loading class from storage + * Instances of this type represent entity types which are abstract - represented entity type cannot + * be instantiated. * - * - * These entity types are not Java classes, but interfaces. + * Instead, some child class represented by a {@link ConcreteEntityType} will be instantiated when + * loading class from storage. + *

+ * These entity types are either abstract classes or interfaces. * * @param Entity type being represented by this instance */ -public class AbstractEntityType extends IdentifiableEntityType { - public AbstractEntityType(String name, Class javaType, IRI iri) { - super(name, javaType, iri); +public class AbstractEntityType extends IdentifiableEntityType { + + public AbstractEntityType(Class javaType, IRI iri) { + super(javaType, iri); } @Override 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 47c72a715..4c5ca9da3 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 @@ -17,7 +17,14 @@ */ package cz.cvut.kbss.jopa.model.metamodel; -import java.util.*; +import cz.cvut.kbss.jopa.exception.AbstractTypeException; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; /** @@ -74,7 +81,8 @@ void setSupertypes(Set> supertypes) { supertypes.forEach(supertype -> supertype.addSubtype(this)); /// find non-abstract parent (class), and use it later for finding attributes, as attributes can be only in AITs that represent classes - supertypes.stream().filter(ait -> !ait.getJavaType().isInterface()).findAny().ifPresent(clsSupertype -> this.classSupertype = clsSupertype); + supertypes.stream().filter(ait -> !ait.getJavaType().isInterface()).findAny() + .ifPresent(clsSupertype -> this.classSupertype = clsSupertype); } private void addSubtype(AbstractIdentifiableType subtype) { @@ -128,6 +136,17 @@ public boolean hasSubtypes() { */ public abstract boolean isAbstract(); + /** + * Gets the Java type represented by the metamodel instance that can be instantiated. + * + * The purpose of this method is mainly to return the generated subclass of {@link #getJavaType()} that is used for + * instantiation. + * @return Instantiable Java type + */ + public Class getInstantiableJavaType() { + throw new AbstractTypeException("Type " + getJavaType() + " is an abstract type and cannot be instantiated!"); + } + public Set> getSubtypes() { return subtypes != null ? Collections.unmodifiableSet(subtypes) : Collections.emptySet(); } @@ -216,14 +235,20 @@ public CollectionAttribute getCollection(String name, Class return getPluralAttribute("Collection", name, elementType, CollectionAttribute.class); } - private > R getPluralAttribute(String type, String name, Class elementType, Class attType) { + private > R getPluralAttribute(String type, String name, + Class elementType, + Class attType) { final Attribute a = getAttribute(name); checkPluralAttribute(a, type, name, elementType, attType, false); return attType.cast(a); } - private > void checkPluralAttribute(Attribute att, String type, String name, Class elementType, Class attType, boolean declared) { + private > void checkPluralAttribute(Attribute att, + String type, String name, + Class elementType, + Class attType, + boolean declared) { if (!attType.isAssignableFrom(att.getClass())) { throw pluralAttNotFound(type, name, elementType, declared); } @@ -234,7 +259,8 @@ public CollectionAttribute getCollection(String name, Class } } - private IllegalArgumentException pluralAttNotFound(String type, String name, Class elementType, boolean declared) { + private IllegalArgumentException pluralAttNotFound(String type, String name, Class elementType, + boolean declared) { return new IllegalArgumentException(type + " attribute " + name + " with element type " + elementType + " is not " + (declared ? "declared" : "present") + " in type " + this); } @@ -318,12 +344,14 @@ private IllegalArgumentException singularAttNotFound(String name, Class type, @Override public Set> getDeclaredPluralAttributes() { - return declaredAttributes.values().stream().filter(Attribute::isCollection).map(a -> (PluralAttribute) a).collect(Collectors.toSet()); + return declaredAttributes.values().stream().filter(Attribute::isCollection) + .map(a -> (PluralAttribute) a).collect(Collectors.toSet()); } @Override public Set> getDeclaredSingularAttributes() { - return declaredAttributes.values().stream().filter(att -> !att.isCollection()).map(a -> (SingularAttribute) a).collect(Collectors.toSet()); + return declaredAttributes.values().stream().filter(att -> !att.isCollection()) + .map(a -> (SingularAttribute) a).collect(Collectors.toSet()); } @Override @@ -339,7 +367,9 @@ public CollectionAttribute getDeclaredCollection(String name, Class return getDeclaredPluralAttribute("Collection", name, elementType, CollectionAttribute.class); } - private > R getDeclaredPluralAttribute(String type, String name, Class elementType, Class attType) { + private > R getDeclaredPluralAttribute(String type, String name, + Class elementType, + Class attType) { final Attribute a = getDeclaredAttribute(name); checkPluralAttribute(a, type, name, elementType, attType, true); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractPluralAttribute.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractPluralAttribute.java index bd5b100df..4caa964f9 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractPluralAttribute.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AbstractPluralAttribute.java @@ -71,7 +71,7 @@ public Class getJavaType() { return collectionType; } - public abstract static class PluralAttributeBuilder extends AbstractAttributeBuilder { + abstract static class PluralAttributeBuilder extends AbstractAttributeBuilder { private Type elementType; private Class collectionType; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AnnotatedAccessor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AnnotatedAccessor.java index bc44119cb..7c5bb091d 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AnnotatedAccessor.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/AnnotatedAccessor.java @@ -25,12 +25,11 @@ /** * Class used for extracting field name from getter or setter. */ - public abstract class AnnotatedAccessor { - private static final String GET_PREFIX="get"; - private static final String SET_PREFIX="set"; - private static final String IS_PREFIX="is"; - private static final String HAS_PREFIX="has"; + public static final String GET_PREFIX = "get"; + public static final String SET_PREFIX = "set"; + public static final String IS_PREFIX = "is"; + public static final String HAS_PREFIX = "has"; protected final Method method; protected final String propertyName; @@ -48,7 +47,7 @@ public static AnnotatedAccessor from(Method method) { } else if (isGetter(method)) { return new AnnotatedGetter(method); } else { - throw new MetamodelInitializationException("Method {} is neither a setter or getter. Only simple getters and setters following the Java naming convection can be used for OWL property annotations."); + throw new MetamodelInitializationException("Method " + method + " is neither a setter nor a getter. Only simple getters and setters following the Java naming convection can be used for OWL property annotations."); } } @@ -62,14 +61,15 @@ private static boolean isSetter(Method m) { } private static boolean isGetter(Method m) { - if (m.getParameterCount() != 0 || m.getReturnType().equals(Void.TYPE)) { /// getter has 0 arguments and returns something + if (m.getParameterCount() != 0 || m.getReturnType() + .equals(Void.TYPE)) { /// getter has 0 arguments and returns something return false; } if (m.getName().startsWith(GET_PREFIX)) { return true; - } else if (m.getReturnType() - .isAssignableFrom(Boolean.class)) {/// getter on bools - can start with is,get or has + } else if (m.getReturnType().isAssignableFrom(Boolean.class) || m.getReturnType() + .isAssignableFrom(boolean.class)) {/// getter on bools - can start with is,get or has return m.getName().startsWith(IS_PREFIX) || m.getName().startsWith(HAS_PREFIX); } else { return false; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ClassFieldMetamodelProcessor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ClassFieldMetamodelProcessor.java index 872baa477..f3200e18a 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ClassFieldMetamodelProcessor.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ClassFieldMetamodelProcessor.java @@ -19,10 +19,22 @@ import cz.cvut.kbss.jopa.exception.MetamodelInitializationException; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; -import cz.cvut.kbss.jopa.model.BeanListenerAspect; import cz.cvut.kbss.jopa.model.IRI; +import cz.cvut.kbss.jopa.model.annotations.Convert; +import cz.cvut.kbss.jopa.model.annotations.Enumerated; +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.OWLAnnotationProperty; +import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; +import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; +import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraint; +import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraints; import cz.cvut.kbss.jopa.model.annotations.Properties; -import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.jopa.model.annotations.RDFCollection; +import cz.cvut.kbss.jopa.model.annotations.Sequence; +import cz.cvut.kbss.jopa.model.annotations.Sparql; +import cz.cvut.kbss.jopa.model.annotations.Types; import cz.cvut.kbss.jopa.oom.converter.ConverterWrapper; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import org.slf4j.Logger; @@ -32,7 +44,14 @@ import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.*; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; class ClassFieldMetamodelProcessor { @@ -94,10 +113,6 @@ void processField(Field field) { return; } - if (isAspectIntegrationField(field)) { - return; - } - if (isIdentifierField(field)) { processIdentifierField(field); return; @@ -114,9 +129,9 @@ void processField(Field field) { } /** - * Do a bottom top search of hierarchy in order to find annotated accessor belonging to given field. - * This is used when a property annotation ( {@link OWLDataProperty,OWLObjectProperty,OWLAnnotationProperty) - * is declared on method, not on field. + * Do a bottom top search of hierarchy in order to find annotated accessor belonging to given field. This is used + * when a property annotation ( + * {@link OWLDataProperty,OWLObjectProperty,OWLAnnotationProperty) is declared on method, not on field. */ private AnnotatedAccessor findAnnotatedMethodBelongingToField(Field field) { LOG.debug("finding property definition to field {} ", field); @@ -236,7 +251,8 @@ private static boolean isTypesField(Field field) { private void processTypesField(Field field, Class fieldValueCls, InferenceInfo inference) { Types tt = field.getAnnotation(Types.class); mappingValidator.validateTypesField(field); - final FetchType fetchType = inference.inferred ? FetchType.EAGER : tt.fetchType(); + // Always use eager for Types, they are fetched eagerly anyway to ensure entity is of correct type + final FetchType fetchType = FetchType.EAGER; et.addDirectTypes(new TypesSpecificationImpl<>(et, fetchType, field, fieldValueCls, inference.inferred)); } @@ -334,6 +350,9 @@ private void createQueryAttribute(Field field, Class fieldValueCls) { private AbstractAttribute createListAttribute(PropertyInfo property, InferenceInfo inference, PropertyAttributes propertyAttributes) { + if (property.getAnnotation(RDFCollection.class) != null) { + return createRdfCollectionAttribute(property, inference, propertyAttributes); + } final Sequence os = property.getAnnotation(Sequence.class); if (os == null) { throw new MetamodelInitializationException("Expected Sequence annotation."); @@ -343,14 +362,25 @@ private void createQueryAttribute(Field field, Class fieldValueCls) { .propertyInfo(property) .inferred(inference.inferred) .includeExplicit(inference.includeExplicit) - .owlListClass(IRI.create(resolvePrefix(os.ClassOWLListIRI()))) - .hasNextProperty(IRI.create(resolvePrefix(os.ObjectPropertyHasNextIRI()))) - .hasContentsProperty(IRI.create(resolvePrefix(os.ObjectPropertyHasContentsIRI()))) + .owlListClass(IRI.create(resolvePrefix(os.listClassIRI()))) + .hasNextProperty(IRI.create(resolvePrefix(os.hasNextPropertyIRI()))) + .hasContentsProperty(IRI.create(resolvePrefix(os.hasContentsPropertyIRI()))) .sequenceType(os.type()); context.getConverterResolver().resolveConverter(property, propertyAttributes).ifPresent(builder::converter); return builder.build(); } + private AbstractAttribute createRdfCollectionAttribute(PropertyInfo property, InferenceInfo inference, + PropertyAttributes propertyAttributes) { + final RDFCollectionAttribute.RDFCollectionAttributeBuilder builder = RDFCollectionAttribute.builder(propertyAttributes) + .declaringType(et) + .propertyInfo(property) + .inferred(inference.inferred) + .includeExplicit(inference.includeExplicit); + context.getConverterResolver().resolveConverter(property, propertyAttributes).ifPresent(builder::converter); + return builder.build(); + } + private String resolvePrefix(String value) { return context.resolveNamespace(value); } @@ -370,12 +400,6 @@ private void processIdentifierField(Field field) { et.setIdentifier(new IRIIdentifierImpl<>(et, field, id.generated())); } - private static boolean isAspectIntegrationField(Field field) { - // AspectJ integration fields cannot be declared transitive (they're generated by the AJC), so we have to - // skip them manually - return field.getType().equals(BeanListenerAspect.Manageable.class); - } - private static boolean isIdentifierField(Field field) { return field.getAnnotation(Id.class) != null; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/CollectionAttributeImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/CollectionAttributeImpl.java index cc46b9e5d..bf743ebbf 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/CollectionAttributeImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/CollectionAttributeImpl.java @@ -37,11 +37,11 @@ public String toString() { return "CollectionAttribute[" + getName() + "]"; } - public static PluralAttributeBuilder builder(PropertyAttributes config) { + static PluralAttributeBuilder builder(PropertyAttributes config) { return new CollectionAttributeBuilder().collectionType(Set.class).config(config); } - public static class CollectionAttributeBuilder extends PluralAttributeBuilder, V> { + static class CollectionAttributeBuilder extends PluralAttributeBuilder, V> { @Override public CollectionAttributeImpl build() { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ConcreteEntityType.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ConcreteEntityType.java index b78d76cdf..872625927 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ConcreteEntityType.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ConcreteEntityType.java @@ -28,12 +28,23 @@ */ public class ConcreteEntityType extends IdentifiableEntityType { - public ConcreteEntityType(String name, Class javaType, IRI iri) { - super(name, javaType, iri); + /** + * The java type that can be used to instantiate an object of the entity class represented by this entity type. + */ + private final Class instantiableType; + + public ConcreteEntityType(Class javaType, Class instantiableType, IRI iri) { + super(javaType, iri); + this.instantiableType = instantiableType; } @Override public boolean isAbstract() { return false; } + + @Override + public Class getInstantiableJavaType() { + return instantiableType; + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ConverterResolver.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ConverterResolver.java index c0cbaaac4..c0423194e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ConverterResolver.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ConverterResolver.java @@ -145,7 +145,7 @@ private static void verifyTypeIsString(PropertyInfo field, Class attValueType private boolean isMultilingualReferencedList(Class elemType, PropertyInfo field) { return MultilingualString.class.isAssignableFrom(elemType) && field.getAnnotation(Sequence.class) != null - && List.class.isAssignableFrom(field.getField().getType()); + && List.class.isAssignableFrom(field.field().getType()); } public static Class resolveConverterAttributeType(Class converterType) { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/DataPropertyAttributes.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/DataPropertyAttributes.java index 7eded98dd..c8a2b2435 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/DataPropertyAttributes.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/DataPropertyAttributes.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.model.metamodel; import cz.cvut.kbss.jopa.model.IRI; +import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; class DataPropertyAttributes extends PropertyAttributes { @@ -35,7 +36,7 @@ void resolve(PropertyInfo propertyInfo, MetamodelBuilder metamodelBuilder, Class this.persistentAttributeType = Attribute.PersistentAttributeType.DATA; this.iri = IRI.create(typeBuilderContext.resolveNamespace(odp.iri())); - this.fetchType = odp.fetch(); + this.fetchType = FetchType.EAGER; this.type = BasicTypeImpl.get(fieldValueCls); this.lexicalForm = odp.lexicalForm(); this.simpleLiteral = odp.simpleLiteral(); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleCallbackResolver.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleCallbackResolver.java index 83b147e9d..8f497eddc 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleCallbackResolver.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleCallbackResolver.java @@ -74,7 +74,7 @@ private void resolveLifecycleCallbacks() { } private void verifyCallbackNotAlreadyDefined(LifecycleEvent hookType) { - if (manager.hasLifecycleCallback(hookType)) { + if (manager.hasEntityLifecycleCallback(hookType)) { throw new MetamodelInitializationException("The type [" + managedType.getJavaType().getName() + "] has multiple lifecycle callbacks for the lifecycle event [" + hookType + "]."); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleListenerManager.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleListenerManager.java index 1557c8d01..2cc9eb4f4 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleListenerManager.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleListenerManager.java @@ -23,7 +23,16 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; /** * Manages entity lifecycle callbacks declared either in the entity (entity lifecycle callbacks) or in its entity @@ -254,7 +263,7 @@ Map getLifecycleCallbacks() { return Collections.unmodifiableMap(lifecycleCallbacks); } - boolean hasLifecycleCallback(LifecycleEvent event) { + boolean hasEntityLifecycleCallback(LifecycleEvent event) { return lifecycleCallbacks.containsKey(event); } @@ -280,4 +289,17 @@ boolean hasEntityListenerCallback(Object listener, LifecycleEvent event) { return entityListenerCallbacks != null && entityListenerCallbacks.containsKey(listener) && entityListenerCallbacks.get(listener).containsKey(event); } + + /** + * Checks whether there is a lifecycle callback defined for the specified event. + *

+ * This checks both callbacks declared in the entity class and in an entity listener class. + * + * @param event Lifecycle event to find callback for + * @return {@code true} if there is a matching callback, {@code false} otherwise + */ + public boolean hasLifecycleCallback(LifecycleEvent event) { + return hasEntityLifecycleCallback(event) || getEntityListeners().stream() + .anyMatch(listener -> hasEntityListenerCallback(listener, event)); + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableEntityType.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableEntityType.java index 4b9c76443..066fbe16b 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableEntityType.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableEntityType.java @@ -34,9 +34,9 @@ public abstract class IdentifiableEntityType extends AbstractIdentifiableType private InheritanceType inheritanceType; - public IdentifiableEntityType(String name, Class javaType, final IRI iri) { + public IdentifiableEntityType(Class javaType, final IRI iri) { super(javaType); - this.name = name; + this.name = javaType.getSimpleName(); this.iri = iri; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ListAttributeImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ListAttributeImpl.java index 18d88afa7..880b85d43 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ListAttributeImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ListAttributeImpl.java @@ -26,19 +26,19 @@ public class ListAttributeImpl extends AbstractPluralAttribute, V> implements ListAttribute { - private final IRI owlListClass; + private final IRI listClass; - private final IRI owlObjectPropertyHasNext; + private final IRI hasNextProperty; - private final IRI owlPropertyHasContents; + private final IRI hasContentsProperty; private final SequenceType owlSequenceType; - private ListAttributeImpl(ListAttributeBuilder builder) { + ListAttributeImpl(ListAttributeBuilder builder) { super(builder); - this.owlListClass = builder.owlListClass; - this.owlObjectPropertyHasNext = builder.owlObjectPropertyHasNext; - this.owlPropertyHasContents = builder.owlPropertyHasContents; + this.listClass = builder.listClass; + this.hasNextProperty = builder.hasNextProperty; + this.hasContentsProperty = builder.hasContentsProperty; this.owlSequenceType = builder.owlSequenceType; } @@ -48,18 +48,18 @@ public CollectionType getCollectionType() { } @Override - public IRI getOWLListClass() { - return owlListClass; + public IRI getListClassIRI() { + return listClass; } @Override - public IRI getOWLObjectPropertyHasNextIRI() { - return owlObjectPropertyHasNext; + public IRI getHasNextPropertyIRI() { + return hasNextProperty; } @Override - public IRI getOWLPropertyHasContentsIRI() { - return owlPropertyHasContents; + public IRI getHasContentsPropertyIRI() { + return hasContentsProperty; } @Override @@ -67,19 +67,24 @@ public SequenceType getSequenceType() { return owlSequenceType; } + @Override + public boolean isRDFCollection() { + return false; + } + @Override public String toString() { return "ListAttribute[" + getName() + "]"; } - public static ListAttributeBuilder builder(PropertyAttributes config) { + static ListAttributeBuilder builder(PropertyAttributes config) { return new ListAttributeBuilder().collectionType(List.class).config(config); } - public static class ListAttributeBuilder extends PluralAttributeBuilder, V> { - private IRI owlListClass; - private IRI owlObjectPropertyHasNext; - private IRI owlPropertyHasContents; + static class ListAttributeBuilder extends PluralAttributeBuilder, V> { + private IRI listClass; + private IRI hasNextProperty; + private IRI hasContentsProperty; private SequenceType owlSequenceType; @Override @@ -125,17 +130,17 @@ public ListAttributeBuilder converter(ConverterWrapper converter) { } public ListAttributeBuilder owlListClass(IRI owlListClass) { - this.owlListClass = owlListClass; + this.listClass = owlListClass; return this; } public ListAttributeBuilder hasNextProperty(IRI hasNextProperty) { - this.owlObjectPropertyHasNext = hasNextProperty; + this.hasNextProperty = hasNextProperty; return this; } public ListAttributeBuilder hasContentsProperty(IRI hasContentsProperty) { - this.owlPropertyHasContents = hasContentsProperty; + this.hasContentsProperty = hasContentsProperty; return this; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ManagedClassProcessor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ManagedClassProcessor.java index f81f20760..953999811 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ManagedClassProcessor.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/ManagedClassProcessor.java @@ -23,6 +23,9 @@ import cz.cvut.kbss.jopa.model.annotations.Namespace; import cz.cvut.kbss.jopa.model.annotations.Namespaces; import cz.cvut.kbss.jopa.model.annotations.OWLClass; +import cz.cvut.kbss.jopa.model.metamodel.gen.ManageableClassGenerator; +import cz.cvut.kbss.jopa.utils.ChangeTrackingMode; +import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.jopa.utils.NamespaceResolver; import java.lang.reflect.AnnotatedElement; @@ -41,11 +44,11 @@ private ManagedClassProcessor() { throw new AssertionError(); } - static TypeBuilderContext processManagedType(Class cls) { + static TypeBuilderContext processManagedType(Class cls, Configuration config) { final NamespaceResolver resolver = detectNamespaces(cls); final AbstractIdentifiableType type; if (isEntityType(cls)) { - type = processEntityType(cls, resolver); + type = processEntityType(cls, resolver, config); } else if (isMappedSuperclassType(cls)) { type = processMappedSuperclassType(cls); } else { @@ -105,15 +108,25 @@ private static void resolveNamespaces(AnnotatedElement target, NamespaceResolver } } - private static IdentifiableEntityType processEntityType(Class cls, NamespaceResolver namespaceResolver) { + private static IdentifiableEntityType processEntityType(Class cls, NamespaceResolver namespaceResolver, + Configuration config) { final OWLClass c = cls.getDeclaredAnnotation(OWLClass.class); assert c != null; if (cls.isInterface() || Modifier.isAbstract(cls.getModifiers())) { - return new AbstractEntityType<>(cls.getSimpleName(), cls, IRI.create(namespaceResolver.resolveFullIri(c.iri()))); + return new AbstractEntityType<>(cls, IRI.create(namespaceResolver.resolveFullIri(c.iri()))); } else { checkForNoArgConstructor(cls); - return new ConcreteEntityType<>(cls.getSimpleName(), cls, IRI.create(namespaceResolver.resolveFullIri(c.iri()))); + final Class instantiableType = resolveInstantiableType(cls, config); + return new ConcreteEntityType<>(cls, instantiableType, IRI.create(namespaceResolver.resolveFullIri(c.iri()))); + } + } + + private static Class resolveInstantiableType(Class cls, Configuration config) { + if (ChangeTrackingMode.IMMEDIATE == ChangeTrackingMode.resolve(config)) { + return new ManageableClassGenerator(config).generate(cls); + } else { + return cls; } } @@ -121,7 +134,7 @@ private static void checkForNoArgConstructor(Class cls) { try { cls.getDeclaredConstructor(); } catch (NoSuchMethodException e) { - throw new MetamodelInitializationException("Class " + cls + " is missing required no-arg constructor.", e); + throw new MetamodelInitializationException("Entity " + cls + " is missing required no-arg constructor.", e); } } @@ -138,7 +151,7 @@ static Class getManagedSuperClass(Class cls) { return null; } - public static Set> getManagedSuperInterfaces(Class cls) { + static Set> getManagedSuperInterfaces(Class cls) { return Arrays.stream(cls.getInterfaces()).filter(ManagedClassProcessor::isManagedType) .map(clazz -> (Class) clazz) .collect(Collectors.toSet()); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilder.java index 492a9cee6..004d379ef 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilder.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilder.java @@ -21,7 +21,11 @@ import cz.cvut.kbss.jopa.loaders.PersistenceUnitClassFinder; import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; import cz.cvut.kbss.jopa.model.TypeReferenceMap; -import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.jopa.model.annotations.Inheritance; +import cz.cvut.kbss.jopa.model.annotations.InheritanceType; +import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty; +import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; +import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; import cz.cvut.kbss.jopa.query.NamedQueryManager; import cz.cvut.kbss.jopa.query.ResultSetMappingManager; import cz.cvut.kbss.jopa.query.mapper.ResultSetMappingProcessor; @@ -32,8 +36,13 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.*; -import java.util.stream.Collectors; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; public class MetamodelBuilder { @@ -90,7 +99,7 @@ private void processOWLClass(final Class cls) { LOG.debug("Processing OWL class: {}", cls); - final TypeBuilderContext et = ManagedClassProcessor.processManagedType(cls); + final TypeBuilderContext et = ManagedClassProcessor.processManagedType(cls, configuration); et.setConverterResolver(converterResolver); et.setPuLanguage(configuration.get(JOPAPersistenceProperties.LANG)); @@ -99,8 +108,8 @@ private void processOWLClass(final Class cls) { private void processMethods(Class cls, AbstractIdentifiableType type) { Arrays.stream(cls.getDeclaredMethods()) - .filter(MetamodelBuilder::isOWLPropertyMethod) - .forEach(m -> addAnnotatedAccessor(type, AnnotatedAccessor.from(m))); + .filter(MetamodelBuilder::isOWLPropertyMethod) + .forEach(m -> addAnnotatedAccessor(type, AnnotatedAccessor.from(m))); } private static boolean isOWLPropertyMethod(Method m) { @@ -163,7 +172,7 @@ private Set> processSupertypes(Class if (typeMap.containsKey(managedSupertype)) { superTypes.add((AbstractIdentifiableType) typeMap.get(managedSupertype)); } else { - final TypeBuilderContext context = ManagedClassProcessor.processManagedType(managedSupertype); + final TypeBuilderContext context = ManagedClassProcessor.processManagedType(managedSupertype, configuration); context.setConverterResolver(converterResolver); context.setPuLanguage(configuration.get(JOPAPersistenceProperties.LANG)); processManagedType(context); @@ -175,16 +184,16 @@ private Set> processSupertypes(Class private static boolean canDeclareInheritanceStrategy(IdentifiableType et) { return et.getSupertypes() == null || et.getSupertypes() - .stream() - .noneMatch(supertype -> supertype.getPersistenceType() == Type.PersistenceType.ENTITY); + .stream() + .noneMatch(supertype -> supertype.getPersistenceType() == Type.PersistenceType.ENTITY); } private static InheritanceType getInheritanceTypeFromParents(IdentifiableEntityType et) { List superTypesInheritanceTypes = et.getSupertypes().stream() - .filter(superType -> superType.getPersistenceType() == Type.PersistenceType.ENTITY) - .map(abstractIdentifiableType -> ((IdentifiableEntityType) abstractIdentifiableType).getInheritanceType()) - .distinct() - .collect(Collectors.toList()); + .filter(superType -> superType.getPersistenceType() == Type.PersistenceType.ENTITY) + .map(abstractIdentifiableType -> ((IdentifiableEntityType) abstractIdentifiableType).getInheritanceType()) + .distinct() + .toList(); if (superTypesInheritanceTypes.size() == 1) { /// there is an agreement from all parents on inheritance type return superTypesInheritanceTypes.get(0); } else { @@ -223,7 +232,7 @@ public AbstractIdentifiableType entity(Class cls) { public Map, EntityType> getEntities() { final Map, EntityType> map = new HashMap<>(); typeMap.entrySet().stream().filter(e -> e.getValue().getPersistenceType() == Type.PersistenceType.ENTITY) - .forEach(e -> map.put(e.getKey(), (EntityType) e.getValue())); + .forEach(e -> map.put(e.getKey(), (EntityType) e.getValue())); return map; } @@ -272,6 +281,6 @@ public TypeReferenceMap getTypeReferenceMap() { } public Set getAnnotatedAccessorsForClass(IdentifiableType k) { - return annotatedAccessors.getOrDefault(k,Collections.emptySet()); + return annotatedAccessors.getOrDefault(k, Collections.emptySet()); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/PropertyInfo.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/PropertyInfo.java index c73d62363..946f8cfad 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/PropertyInfo.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/PropertyInfo.java @@ -35,7 +35,7 @@ public interface PropertyInfo { Member getMember(); - Field getField(); + Field field(); static PropertyInfo from(Field field) { return new FieldInfo(field); @@ -45,47 +45,37 @@ static PropertyInfo from(Method method,Field field) { return new MethodInfo(method,field); } - class FieldInfo implements PropertyInfo { - private final Field field; - - public FieldInfo(Field field) { - this.field = field; - } - - @Override - public Class getType() { - return field.getType(); - } - - @Override - public String getName() { - return field.getName(); - } - - @Override - public Member getMember() { - return field; - } - - @Override - public T getAnnotation(Class annotationClass) { - return field.getAnnotation(annotationClass); - } + record FieldInfo(Field field) implements PropertyInfo { @Override - public Field getField() { - return field; + public Class getType() { + return field.getType(); + } + + @Override + public String getName() { + return field.getName(); + } + + @Override + public Member getMember() { + return field; + } + + @Override + public T getAnnotation(Class annotationClass) { + return field.getAnnotation(annotationClass); + } + + @Override + public String toString() { + return "FieldInfo{" + + "class =" + field.getDeclaringClass().getName() + + ", field=" + field.getName() + + '}'; + } } - @Override - public String toString() { - return "FieldInfo{" + - "class =" + field.getDeclaringClass().getName() + - ", field=" + field.getName() + - '}'; - } - } - class MethodInfo implements PropertyInfo { private final Method method; @@ -116,7 +106,7 @@ public T getAnnotation(Class annotationClass) { } @Override - public Field getField() { + public Field field() { return rawField; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/RDFCollectionAttribute.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/RDFCollectionAttribute.java new file mode 100644 index 000000000..521837230 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/RDFCollectionAttribute.java @@ -0,0 +1,91 @@ +package cz.cvut.kbss.jopa.model.metamodel; + +import cz.cvut.kbss.jopa.model.IRI; +import cz.cvut.kbss.jopa.model.annotations.SequenceType; +import cz.cvut.kbss.jopa.oom.converter.ConverterWrapper; +import cz.cvut.kbss.jopa.vocabulary.RDF; + +import java.util.List; + +public class RDFCollectionAttribute extends ListAttributeImpl { + + RDFCollectionAttribute(RDFCollectionAttributeBuilder builder) { + super(builder); + } + + @Override + public boolean isRDFCollection() { + return true; + } + + @Override + public String toString() { + return "RDFCollectionAttribute[" + getName() + "]"; + } + + static RDFCollectionAttributeBuilder builder(PropertyAttributes config) { + return new RDFCollectionAttributeBuilder().collectionType(List.class).config(config); + } + + static class RDFCollectionAttributeBuilder extends ListAttributeBuilder { + + RDFCollectionAttributeBuilder() { + owlListClass(IRI.create(RDF.LIST)); + hasNextProperty(IRI.create(RDF.REST)); + hasContentsProperty(IRI.create(RDF.FIRST)); + sequenceType(SequenceType.referenced); + } + + @Override + public RDFCollectionAttributeBuilder config(PropertyAttributes config) { + super.config(config); + return this; + } + + @Override + public RDFCollectionAttributeBuilder collectionType(Class> collectionType) { + super.collectionType(collectionType); + return this; + } + + @Override + public RDFCollectionAttributeBuilder elementType(Type elementType) { + super.elementType(elementType); + return this; + } + + @Override + public RDFCollectionAttributeBuilder propertyInfo(PropertyInfo propertyInfo) { + super.propertyInfo(propertyInfo); + return this; + } + + @Override + public RDFCollectionAttributeBuilder declaringType(ManagedType declaringType) { + super.declaringType(declaringType); + return this; + } + + @Override + public RDFCollectionAttributeBuilder inferred(boolean inferred) { + super.inferred(inferred); + return this; + } + + @Override + public RDFCollectionAttributeBuilder includeExplicit(boolean includeExplicit) { + super.includeExplicit(includeExplicit); + return this; + } + + @Override + public RDFCollectionAttributeBuilder converter(ConverterWrapper converter) { + super.converter(converter); + return this; + } + + public RDFCollectionAttribute build() { + return new RDFCollectionAttribute<>(this); + } + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SetAttributeImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SetAttributeImpl.java index 0c1916c25..3cd356986 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SetAttributeImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/SetAttributeImpl.java @@ -36,11 +36,11 @@ public String toString() { return "SetAttribute[" + getName() + "]"; } - public static PluralAttributeBuilder builder(PropertyAttributes config) { + static PluralAttributeBuilder builder(PropertyAttributes config) { return new SetAttributeBuilder().collectionType(Set.class).config(config); } - public static class SetAttributeBuilder extends PluralAttributeBuilder, V> { + static class SetAttributeBuilder extends PluralAttributeBuilder, V> { @Override public SetAttributeImpl build() { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/GeneratedEntityClass.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/GeneratedEntityClass.java new file mode 100644 index 000000000..f57dd15df --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/GeneratedEntityClass.java @@ -0,0 +1,14 @@ +package cz.cvut.kbss.jopa.model.metamodel.gen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation for dynamically generated classes. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface GeneratedEntityClass { +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/GeneratedEntityClassImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/GeneratedEntityClassImpl.java new file mode 100644 index 000000000..ea61660dc --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/GeneratedEntityClassImpl.java @@ -0,0 +1,11 @@ +package cz.cvut.kbss.jopa.model.metamodel.gen; + +import java.lang.annotation.Annotation; + +public class GeneratedEntityClassImpl implements GeneratedEntityClass { + + @Override + public Class annotationType() { + return GeneratedEntityClass.class; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/ManageableClassGenerator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/ManageableClassGenerator.java new file mode 100644 index 000000000..d53b428ba --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/ManageableClassGenerator.java @@ -0,0 +1,105 @@ +package cz.cvut.kbss.jopa.model.metamodel.gen; + +import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; +import cz.cvut.kbss.jopa.model.Manageable; +import cz.cvut.kbss.jopa.model.metamodel.AnnotatedAccessor; +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.validator.AttributeModificationValidator; +import cz.cvut.kbss.jopa.utils.Configuration; +import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; +import cz.cvut.kbss.jopa.utils.MetamodelUtils; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.NamingStrategy; +import net.bytebuddy.description.modifier.FieldPersistence; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.SuperMethodCall; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.This; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Objects; + +import static net.bytebuddy.matcher.ElementMatchers.isSetter; + +/** + * Generates persistence context-aware classes that implement the {@link cz.cvut.kbss.jopa.model.Manageable} interface. + *

+ * Such classes have an additional attribute not inherited from the base entity class. This attribute's value is a + * reference to the persistence context to which an instance is attached. {@link cz.cvut.kbss.jopa.model.Manageable} + * allows establishing and accessing this connection. + */ +public class ManageableClassGenerator implements PersistenceContextAwareClassGenerator { + + private static final Logger LOG = LoggerFactory.getLogger(ManageableClassGenerator.class); + + private final ByteBuddy byteBuddy = new ByteBuddy().with(new NamingStrategy.AbstractBase() { + + @Override + protected String name(TypeDescription typeDescription) { + return "JOPA_" + typeDescription.getSimpleName(); + } + }); + + private final Configuration config; + + public ManageableClassGenerator(Configuration config) {this.config = config;} + + @Override + public Class generate(Class entityClass) { + Objects.requireNonNull(entityClass); + LOG.trace("Generating dynamic type for entity class {}.", entityClass); + DynamicType.Unloaded typeDef = byteBuddy.subclass(entityClass) + .annotateType(entityClass.getAnnotations()) + .annotateType(new GeneratedEntityClassImpl()) + .defineField("persistenceContext", UnitOfWork.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT) + .implement(Manageable.class) + .intercept(FieldAccessor.ofBeanProperty()) + .method(isSetter().and(new PersistentPropertySetterMatcher<>(entityClass))) + .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(SetterInterceptor.class))) + .make(); + LOG.debug("Generated dynamic type {} for entity class {}.", typeDef, entityClass); + outputGeneratedClass(typeDef); + return typeDef.load(getClass().getClassLoader()).getLoaded(); + } + + private void outputGeneratedClass(DynamicType.Unloaded typeDef) { + final String outputDir = config.get(JOPAPersistenceProperties.CLASS_GENERATOR_OUTPUT_DIR, ""); + if (!outputDir.isBlank()) { + final File output = new File(outputDir); + try { + LOG.trace("Saving generated class '{}' to '{}'.", typeDef, output); + typeDef.saveIn(output); + } catch (IOException e) { + LOG.error("Unable to output generated class {} to file {}.", typeDef, output, e); + } + } + } + + public static class SetterInterceptor { + + public static void set(@This Manageable instance, @Origin Method setter) throws Exception { + final UnitOfWork pc = instance.getPersistenceContext(); + if (pc == null || !pc.isInTransaction()) { + return; + } + final String fieldName = AnnotatedAccessor.from(setter).getPropertyName(); + final EntityType et = pc.getMetamodel().entity(MetamodelUtils.getEntityClass(instance.getClass())); + final FieldSpecification fieldSpec = et.getFieldSpecification(fieldName); + if (EntityPropertiesUtils.isFieldTransient(fieldSpec.getJavaField())) { + return; + } + AttributeModificationValidator.verifyCanModify(fieldSpec); + pc.attributeChanged(instance, fieldSpec); + } + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/PersistenceContextAwareClassGenerator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/PersistenceContextAwareClassGenerator.java new file mode 100644 index 000000000..0471a06bd --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/PersistenceContextAwareClassGenerator.java @@ -0,0 +1,16 @@ +package cz.cvut.kbss.jopa.model.metamodel.gen; + +/** + * Generates classes corresponding to entity classes, but able to be connected to a persistence context. + */ +public interface PersistenceContextAwareClassGenerator { + + /** + * Generates a class extending the specified {@code entityClass} but able to be connected to a persistence context. + * + * @param entityClass Entity class for which to generate the subclass + * @param Entity type + * @return Entity class subtype + */ + Class generate(Class entityClass); +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/PersistentPropertyMatcher.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/PersistentPropertyMatcher.java new file mode 100644 index 000000000..41548af1c --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/PersistentPropertyMatcher.java @@ -0,0 +1,47 @@ +package cz.cvut.kbss.jopa.model.metamodel.gen; + +import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import java.lang.reflect.Field; +import java.util.Objects; +import java.util.Optional; + +/** + * Matches only persistent attribute getters/setters. + *

+ * This matcher checks that there exists a persistent field corresponding to the method name that appears to be a + * getter/setter. + * + * @param MethodDescription + */ +public abstract class PersistentPropertyMatcher extends ElementMatcher.Junction.ForNonNullValues { + + private final Class parentType; + + public PersistentPropertyMatcher(Class parentType) {this.parentType = Objects.requireNonNull(parentType);} + + @Override + protected boolean doMatch(T target) { + final String name = target.getName(); + final Optional fieldName = resolveFieldName(name, target); + return fieldName.flatMap(this::tryFindingField).map(f -> !EntityPropertiesUtils.isFieldTransient(f)) + .orElse(false); + } + + protected abstract Optional resolveFieldName(String methodName, MethodDescription methodDesc); + + protected Optional tryFindingField(String fieldName) { + Class type = parentType; + do { + try { + Field f = type.getDeclaredField(fieldName); + return Optional.of(f); + } catch (NoSuchFieldException e) { + type = type.getSuperclass(); + } + } while (type != null); + return Optional.empty(); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/PersistentPropertySetterMatcher.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/PersistentPropertySetterMatcher.java new file mode 100644 index 000000000..503b13022 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/gen/PersistentPropertySetterMatcher.java @@ -0,0 +1,29 @@ +package cz.cvut.kbss.jopa.model.metamodel.gen; + +import cz.cvut.kbss.jopa.model.metamodel.AnnotatedAccessor; +import net.bytebuddy.description.method.MethodDescription; + +import java.beans.Introspector; +import java.util.Optional; + +/** + * Matches only persistent attribute setters. + *

+ * This matcher checks that there exists a persistent field corresponding to the method name that appears to be a + * setter. + * + * @param MethodDescription + */ +public class PersistentPropertySetterMatcher extends PersistentPropertyMatcher { + + public PersistentPropertySetterMatcher(Class parentType) { + super(parentType); + } + + @Override + protected Optional resolveFieldName(String methodName, MethodDescription methodDesc) { + assert methodName.startsWith(AnnotatedAccessor.SET_PREFIX) && methodDesc.getParameters().size() == 1; + + return Optional.of(Introspector.decapitalize(methodName.substring(AnnotatedAccessor.SET_PREFIX.length()))); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/AxiomDescriptorFactory.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/AxiomDescriptorFactory.java index 90fa755cd..069dd4ab7 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/AxiomDescriptorFactory.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/AxiomDescriptorFactory.java @@ -17,7 +17,6 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.SequenceType; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.Attribute; @@ -28,7 +27,7 @@ import cz.cvut.kbss.jopa.model.metamodel.PluralAttribute; import cz.cvut.kbss.jopa.model.metamodel.PropertiesSpecification; import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; -import cz.cvut.kbss.jopa.sessions.LoadingParameters; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; @@ -52,9 +51,6 @@ AxiomDescriptor createForEntityLoading(LoadingParameters loadingParams, Entit addForTypes(loadingParams, et, descriptor); addForProperties(loadingParams, et, descriptor); for (Attribute att : et.getAttributes()) { - if (!shouldLoad(att.getFetchType(), loadingParams.isForceEager())) { - continue; - } final Assertion a = createAssertion(att, loadingParams.getDescriptor().getAttributeDescriptor(att)); addAssertionToDescriptor(loadingParams.getDescriptor(), att, descriptor, a); } @@ -63,18 +59,20 @@ AxiomDescriptor createForEntityLoading(LoadingParameters loadingParams, Entit private void addForTypes(LoadingParameters loadingParams, EntityType et, AxiomDescriptor descriptor) { final TypesSpecification types = et.getTypes(); - if (types != null && shouldLoad(types.getFetchType(), loadingParams.isForceEager())) { + if (types != null) { final Descriptor entityDesc = loadingParams.getDescriptor(); final Assertion typesAssertion = Assertion.createClassAssertion(includeInferred(types, entityDesc.getAttributeDescriptor(types))); - addAssertionToDescriptor(entityDesc, types, descriptor, typesAssertion); + if (descriptor.containsAssertion(typesAssertion) && !entityDesc.getAttributeContexts(types).isEmpty()) { + // If the types are non-inferred and have context, we need to merge it with the subject context + entityDesc.getAttributeContexts(types).forEach(c -> descriptor.addAssertionContext(typesAssertion, c)); + entityDesc.getContexts().forEach(c -> descriptor.addAssertionContext(typesAssertion, c)); + } else { + addAssertionToDescriptor(entityDesc, types, descriptor, typesAssertion); + } } } - private static boolean shouldLoad(FetchType fetchType, boolean forceLoad) { - return fetchType != FetchType.LAZY || forceLoad; - } - private void addAssertionToDescriptor(Descriptor entityDescriptor, FieldSpecification att, final AxiomDescriptor descriptor, final Assertion assertion) { descriptor.addAssertion(assertion); @@ -89,7 +87,7 @@ private void addAssertionToDescriptor(Descriptor entityDescriptor, FieldSpecific private void addForProperties(LoadingParameters loadingParams, EntityType et, AxiomDescriptor descriptor) { final PropertiesSpecification props = et.getProperties(); - if (props != null && shouldLoad(props.getFetchType(), loadingParams.isForceEager())) { + if (props != null) { final Descriptor entityDesc = loadingParams.getDescriptor(); final Assertion propsAssertion = Assertion.createUnspecifiedPropertyAssertion( includeInferred(props, entityDesc.getAttributeDescriptor(props))); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/AxiomValueGatherer.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/AxiomValueGatherer.java index 4dba9eca2..37428981a 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/AxiomValueGatherer.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/AxiomValueGatherer.java @@ -38,8 +38,8 @@ class AxiomValueGatherer { private final AxiomValueDescriptor axiomDescriptor; private final Collection simpleListDescriptors = new ArrayList<>(); private final Collection referencedListDescriptors = new ArrayList<>(); - private Set typesToAdd; - private Set typesToRemove; + private final Set typesToAdd = new HashSet<>(); + private final Set typesToRemove = new HashSet<>(); private URI typesContext; private Map>> propertiesToAdd; private Map>> propertiesToRemove; @@ -77,9 +77,6 @@ void addReferencedListValues(ReferencedListValueDescriptor listDescriptor) { } void addTypes(Set types, URI context) { - if (typesToAdd == null) { - this.typesToAdd = new HashSet<>(types.size()); - } appendTypes(typesToAdd, types, context); } @@ -89,9 +86,6 @@ private void appendTypes(Set target, Set types, URI context) { } void removeTypes(Set types, URI context) { - if (typesToRemove == null) { - this.typesToRemove = new HashSet<>(types.size()); - } appendTypes(typesToRemove, types, context); } @@ -124,7 +118,7 @@ void removeProperties(Map>> properties, URI context) { void persist(Connection connection) { try { connection.persist(axiomDescriptor); - if (typesToAdd != null) { + if (!typesToAdd.isEmpty()) { connection.types().addTypes(axiomDescriptor.getSubject(), typesContext, typesToAdd); } if (propertiesToAdd != null) { @@ -144,10 +138,10 @@ void persist(Connection connection) { void update(Connection connection) { try { connection.update(axiomDescriptor); - if (typesToAdd != null) { + if (!typesToAdd.isEmpty()) { connection.types().addTypes(axiomDescriptor.getSubject(), typesContext, typesToAdd); } - if (typesToRemove != null) { + if (!typesToRemove.isEmpty()) { connection.types().removeTypes(axiomDescriptor.getSubject(), typesContext, typesToRemove); } if (propertiesToAdd != null) { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/DefaultInstanceLoader.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/DefaultInstanceLoader.java index b61482438..4c32d0bf1 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/DefaultInstanceLoader.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/DefaultInstanceLoader.java @@ -17,8 +17,8 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.model.metamodel.EntityType; -import cz.cvut.kbss.jopa.sessions.LoadingParameters; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; /** * Loads entities which do not require polymorphic handling. @@ -31,13 +31,13 @@ private DefaultInstanceLoader(DefaultInstanceLoaderBuilder builder) { @Override T loadEntity(LoadingParameters loadingParameters) { - final EntityType et = metamodel.entity(loadingParameters.getEntityType()); + final IdentifiableEntityType et = metamodel.entity(loadingParameters.getEntityClass()); return loadInstance(loadingParameters, et); } @Override T loadReference(LoadingParameters loadingParameters) { - final EntityType et = metamodel.entity(loadingParameters.getEntityType()); + final IdentifiableEntityType et = metamodel.entity(loadingParameters.getEntityClass()); return loadReferenceInstance(loadingParameters, et); } 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 82af82369..4f818c24c 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 @@ -18,15 +18,28 @@ package cz.cvut.kbss.jopa.oom; import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; +import cz.cvut.kbss.jopa.model.LoadState; import cz.cvut.kbss.jopa.model.TypedQueryImpl; import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.model.metamodel.*; +import cz.cvut.kbss.jopa.model.metamodel.AbstractQueryAttribute; +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.CollectionType; +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.PluralQueryAttribute; +import cz.cvut.kbss.jopa.model.metamodel.PluralQueryAttributeImpl; +import cz.cvut.kbss.jopa.model.metamodel.QueryAttribute; 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.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.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator; +import cz.cvut.kbss.jopa.utils.CollectionFactory; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.jopa.utils.ReflectionUtils; import cz.cvut.kbss.jopa.vocabulary.RDF; @@ -49,31 +62,36 @@ class EntityConstructor { private final ObjectOntologyMapperImpl mapper; - EntityConstructor(ObjectOntologyMapperImpl mapper) { + private final LoadStateDescriptorRegistry loadStateRegistry; + + EntityConstructor(ObjectOntologyMapperImpl mapper, LoadStateDescriptorRegistry loadStateRegistry) { this.mapper = mapper; + this.loadStateRegistry = loadStateRegistry; } /** * Creates an instance of the specified {@link EntityType} with the specified identifier and populates its * attributes from the specified axioms. * - * @param identifier Entity identifier - * @param et Entity type - * @param descriptor Entity descriptor with context info - * @param axioms Axioms from which the instance attribute values should be reconstructed - * @param Entity type + * @param constructionParams Parameters for constructing the entity + * @param axioms Axioms from which the instance attribute values should be reconstructed + * @param Entity type * @return New instance with populated attributes */ - T reconstructEntity(URI identifier, EntityType et, Descriptor descriptor, Collection> axioms) { + T reconstructEntity(EntityConstructionParameters constructionParams, Collection> axioms) { assert !axioms.isEmpty(); + final IdentifiableEntityType et = constructionParams.entityType(); if (!axiomsContainEntityClassAssertion(axioms, et)) { return null; } - final T instance = createEntityInstance(identifier, et); - mapper.registerInstance(identifier, instance); - populateAttributes(instance, et, descriptor, axioms); - populateQueryAttributes(instance, et); + final T instance = createEntityInstance(constructionParams.id(), et); + mapper.registerInstance(constructionParams.id(), instance); + final LoadStateDescriptor loadStateDescriptor = LoadStateDescriptorFactory.createAllUnknown(instance, et); + loadStateRegistry.put(instance, loadStateDescriptor); + populateAttributes(instance, constructionParams, axioms, loadStateDescriptor); + populateQueryAttributes(instance, et, loadStateDescriptor); + processEmptyAttributes(instance, et, loadStateDescriptor); validateIntegrityConstraints(instance, et); return instance; @@ -91,14 +109,16 @@ private static boolean axiomsContainEntityClassAssertion(Collection> ax * @param Entity type * @return Newly created instance with identifier set */ - T createEntityInstance(URI identifier, EntityType et) { + T createEntityInstance(URI identifier, IdentifiableEntityType et) { + // TODO et.getInstantiableJavaType? final T instance = ReflectionUtils.instantiateUsingDefaultConstructor(et.getJavaType()); EntityPropertiesUtils.setIdentifier(identifier, instance, et); return instance; } - private void populateAttributes(final T instance, EntityType et, Descriptor entityDescriptor, - Collection> axioms) { + private void populateAttributes(final T instance, EntityConstructionParameters constructionParams, + Collection> axioms, LoadStateDescriptor loadStateDescriptor) { + final IdentifiableEntityType et = constructionParams.entityType(); final Map> attributes = indexEntityAttributes(et); final Map, FieldStrategy, T>> fieldLoaders = new HashMap<>(et.getAttributes().size()); @@ -107,19 +127,28 @@ private void populateAttributes(final T instance, EntityType et, Descript continue; } final FieldStrategy, T> fs = getFieldLoader( - ax, attributes, fieldLoaders, et, entityDescriptor); + ax, attributes, fieldLoaders, et, constructionParams.descriptor()); if (fs == null) { if (!MappingUtils.isClassAssertion(ax)) { LOG.warn("No attribute found for property {}. Axiom {} will be skipped.", ax.getAssertion(), ax); } continue; } - fs.addValueFromAxiom(ax); + if (fs.attribute.getFetchType() == FetchType.LAZY && !constructionParams.forceEager()) { + fs.lazilyAddAxiomValue(ax); + } else { + fs.addAxiomValue(ax); + } } // 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()) { + loadStateDescriptor.setLoaded((FieldSpecification) fs.attribute, LoadState.NOT_LOADED); + } else { + loadStateDescriptor.setLoaded((FieldSpecification) fs.attribute, LoadState.LOADED); + } } } @@ -161,14 +190,19 @@ private void populateAttributes(final T instance, EntityType et, Descript * @param et the entity class representation in the metamodel * @param the entity class */ - public void populateQueryAttributes(final T instance, EntityType et) { - final SparqlQueryFactory queryFactory = mapper.getUow().getQueryFactory(); + public void populateQueryAttributes(T instance, EntityType et) { + populateQueryAttributes(instance, et, loadStateRegistry.get(instance)); + } + + private void populateQueryAttributes(T instance, EntityType et, LoadStateDescriptor loadStateDescriptor) { + final SparqlQueryFactory queryFactory = mapper.getUow().sparqlQueryFactory(); final Set> queryAttributes = et.getQueryAttributes(); for (QueryAttribute queryAttribute : queryAttributes) { if (queryAttribute.getFetchType() != FetchType.LAZY) { populateQueryAttribute(instance, queryAttribute, queryFactory, et); + loadStateDescriptor.setLoaded(queryAttribute, LoadState.LOADED); } } } @@ -181,14 +215,16 @@ private void populateQueryAttribute(T instance, QueryAttribute PluralQueryAttribute pluralQueryAttribute = (PluralQueryAttribute) queryAttribute; typedQuery = queryFactory.createNativeQuery(pluralQueryAttribute.getQuery(), - pluralQueryAttribute.getElementType().getJavaType()); + pluralQueryAttribute.getElementType().getJavaType()); } else { typedQuery = queryFactory.createNativeQuery(queryAttribute.getQuery(), queryAttribute.getJavaType()); } } catch (RuntimeException e) { - LOG.error("Could not create native query from the parameter given in annotation @Sparql:\n{}" + - "\nAttribute '{}' will be skipped.", queryAttribute.getQuery(), - queryAttribute.getJavaMember().getName(), e); + LOG.error(""" + Could not create native query from the parameter given in annotation @Sparql: + {} + Attribute '{}' will be skipped.""", queryAttribute.getQuery(), + queryAttribute.getJavaMember().getName(), e); return; } @@ -213,7 +249,7 @@ private static void setAttributeQueryParameters(T instance, QueryAttribute query.hasParameter(a.getName())).forEach(a -> { final Object value = EntityPropertiesUtils.getAttributeValue(a, instance); - if (value != null) { + if (value != null && ((!a.isCollection() || !((Collection) value).isEmpty()))) { query.setParameter(a.getName(), value); } }); @@ -235,6 +271,26 @@ 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); + return value == null || (value instanceof Collection && ((Collection) value).isEmpty()); + }) + .forEach(fs -> { + final FetchType fetchType = fs.getFetchType(); + final LoadState loadState = loadStateDescriptor.isLoaded(fs); + if (fs.isCollection() && (fetchType == FetchType.EAGER || fetchType == FetchType.LAZY && loadState == LoadState.UNKNOWN)) { + final CollectionType ct = CollectionFactory.resolveCollectionType(fs.getJavaType()); + final Object emptyValue = ct == CollectionType.MAP ? CollectionFactory.createDefaultMap() : CollectionFactory.createDefaultCollection(ct); + EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, emptyValue); + loadStateDescriptor.setLoaded(fs, LoadState.LOADED); + } else if (fetchType == FetchType.LAZY && loadState == LoadState.UNKNOWN) { + loadStateDescriptor.setLoaded(fs, LoadState.LOADED); + } + }); + } + private void validateIntegrityConstraints(T entity, EntityType et) { if (shouldSkipICValidationOnLoad()) { return; @@ -258,19 +314,27 @@ void validateIntegrityConstraints(T entity, FieldSpecification void setFieldValue(T entity, FieldSpecification fieldSpec, Collection> axioms, EntityType et, Descriptor entityDescriptor) { - if (axioms.isEmpty()) { - validateIntegrityConstraints(entity, fieldSpec, et); - return; - } final FieldStrategy, T> fs = FieldStrategy .createFieldStrategy(et, fieldSpec, entityDescriptor, mapper); - axioms.forEach(fs::addValueFromAxiom); + axioms.forEach(fs::addAxiomValue); fs.buildInstanceFieldValue(entity); validateIntegrityConstraints(entity, fieldSpec, et); } void setQueryAttributeFieldValue(T entity, QueryAttribute queryAttribute, EntityType et) { - final SparqlQueryFactory queryFactory = mapper.getUow().getQueryFactory(); + final SparqlQueryFactory queryFactory = mapper.getUow().sparqlQueryFactory(); populateQueryAttribute(entity, queryAttribute, queryFactory, et); } + + /** + * Parameters for constructing an entity from data. + * + * @param id Entity identifier + * @param entityType Entity type + * @param descriptor Entity descriptor, specifies e.g., repository contexts + * @param forceEager Whether all attributes have to be loaded eagerly + * @param Entity type + */ + record EntityConstructionParameters(URI id, IdentifiableEntityType entityType, Descriptor descriptor, + 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 0e6d6e7e0..6c2cd9283 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 @@ -21,7 +21,7 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.QueryAttribute; -import cz.cvut.kbss.jopa.oom.exceptions.EntityDeconstructionException; +import cz.cvut.kbss.jopa.oom.exception.EntityDeconstructionException; import cz.cvut.kbss.ontodriver.model.NamedResource; import java.net.URI; 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 5cd016acb..588d8fce4 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 @@ -17,15 +17,20 @@ */ package cz.cvut.kbss.jopa.oom; +import cz.cvut.kbss.jopa.datatype.util.Pair; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; import cz.cvut.kbss.jopa.model.MetamodelImpl; 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.IdentifiableEntityType; import cz.cvut.kbss.jopa.model.metamodel.PluralAttribute; -import cz.cvut.kbss.jopa.oom.exceptions.EntityReconstructionException; -import cz.cvut.kbss.jopa.sessions.CacheManager; -import cz.cvut.kbss.jopa.sessions.LoadingParameters; +import cz.cvut.kbss.jopa.oom.exception.EntityReconstructionException; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.ontodriver.Connection; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; @@ -36,8 +41,10 @@ import java.net.URI; import java.util.Collection; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Consumer; /** * Root of the entity loading strategies. @@ -51,6 +58,8 @@ abstract class EntityInstanceLoader { private final AxiomDescriptorFactory descriptorFactory; final EntityConstructor entityBuilder; + final LoadStateDescriptorRegistry loadStateRegistry; + EntityInstanceLoader(EntityInstanceLoaderBuilder builder) { assert builder.storageConnection != null; assert builder.metamodel != null; @@ -63,6 +72,7 @@ abstract class EntityInstanceLoader { this.cache = builder.cache; this.descriptorFactory = builder.descriptorFactory; this.entityBuilder = builder.entityBuilder; + this.loadStateRegistry = builder.loadStateRegistry; } /** @@ -86,7 +96,7 @@ abstract class EntityInstanceLoader { */ abstract T loadReference(LoadingParameters loadingParameters); - U loadInstance(LoadingParameters loadingParameters, EntityType et) { + U loadInstance(LoadingParameters loadingParameters, IdentifiableEntityType et) { final URI identifier = loadingParameters.getIdentifier(); final Descriptor descriptor = loadingParameters.getDescriptor(); if (isCached(loadingParameters, et)) { @@ -95,7 +105,9 @@ U loadInstance(LoadingParameters loadingParameters, EntityTy final AxiomDescriptor axiomDescriptor = descriptorFactory.createForEntityLoading(loadingParameters, et); try { final Collection> axioms = storageConnection.find(axiomDescriptor); - return axioms.isEmpty() ? null : entityBuilder.reconstructEntity(identifier, et, descriptor, axioms); + return axioms.isEmpty() ? null : entityBuilder.reconstructEntity( + new EntityConstructor.EntityConstructionParameters<>(identifier, et, descriptor, loadingParameters.isForceEager()), + axioms); } catch (OntoDriverException e) { throw new StorageAccessException(e); } catch (cz.cvut.kbss.jopa.exception.InstantiationException e) { @@ -110,26 +122,30 @@ boolean isCached(LoadingParameters loadingParameters, EntityType T loadCached(EntityType et, URI identifier, Descriptor descriptor) { final T cached = cache.get(et.getJavaType(), identifier, descriptor); - recursivelyReloadQueryAttributes(cached, et, new IdentityHashMap<>()); + recursivelyProcessCachedEntityReferences(cached, et, new IdentityHashMap<>(), List.of( + pair -> loadStateRegistry.put(pair.first(), getLoadStatDescriptor(pair.first(), pair.second())), + pair -> entityBuilder.populateQueryAttributes(pair.first(), (EntityType) pair.second()) + )); return cached; } - /** - * Recursively reloads query attribute values. - * - * @param instance Instance whose query attributes should be reloaded - * @param et Entity type of the instance - * @param visited Map of already visited objects to prevent infinite recursion - */ - private void recursivelyReloadQueryAttributes(Object instance, EntityType et, Map visited) { + private LoadStateDescriptor getLoadStatDescriptor(Object instance, EntityType et) { + final LoadStateDescriptor cached = cache.getLoadStateDescriptor(instance); + if (cached != null) { + return cached; + } + return LoadStateDescriptorFactory.createAllUnknown(instance, (EntityType) et); + } + + private void recursivelyProcessCachedEntityReferences(Object instance, EntityType et, Map visited, List>>> handlers) { if (visited.containsKey(instance)) { return; } visited.put(instance, null); - entityBuilder.populateQueryAttributes(instance, (EntityType) et); + handlers.forEach(h -> h.accept(new Pair<>(instance, et))); et.getAttributes().stream().filter(Attribute::isAssociation).forEach(att -> { final Class cls = att.isCollection() ? ((PluralAttribute) att).getElementType() - .getJavaType() : att.getJavaType(); + .getJavaType() : att.getJavaType(); if (!metamodel.isEntityType(cls)) { return; } @@ -137,15 +153,15 @@ private void recursivelyReloadQueryAttributes(Object instance, EntityType et, if (value != null) { // Resolve the value class instead of using the attribute type, as it may be a subclass at runtime if (att.isCollection()) { - ((Collection) value).forEach(el -> recursivelyReloadQueryAttributes(el, metamodel.entity(el.getClass()), visited)); + ((Collection) value).forEach(el -> recursivelyProcessCachedEntityReferences(el, metamodel.entity(el.getClass()), visited, handlers)); } else { - recursivelyReloadQueryAttributes(value, metamodel.entity(value.getClass()), visited); + recursivelyProcessCachedEntityReferences(value, metamodel.entity(value.getClass()), visited, handlers); } } }); } - T loadReferenceInstance(LoadingParameters loadingParameters, EntityType et) { + T loadReferenceInstance(LoadingParameters loadingParameters, IdentifiableEntityType et) { final URI identifier = loadingParameters.getIdentifier(); final Axiom typeAxiom = descriptorFactory.createForReferenceLoading(identifier, et); try { @@ -167,6 +183,8 @@ abstract static class EntityInstanceLoaderBuilder { private AxiomDescriptorFactory descriptorFactory; private EntityConstructor entityBuilder; + private LoadStateDescriptorRegistry loadStateRegistry; + EntityInstanceLoaderBuilder connection(Connection connection) { this.storageConnection = Objects.requireNonNull(connection); return this; @@ -192,6 +210,11 @@ EntityInstanceLoaderBuilder cache(CacheManager cache) { return this; } + EntityInstanceLoaderBuilder loadStateRegistry(LoadStateDescriptorRegistry loadStateRegistry) { + this.loadStateRegistry = loadStateRegistry; + return this; + } + abstract EntityInstanceLoader build(); } } 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 12879091b..d1cba35ef 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 @@ -19,9 +19,24 @@ import cz.cvut.kbss.jopa.model.MultilingualString; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.model.metamodel.*; +import cz.cvut.kbss.jopa.model.metamodel.AbstractAttribute; +import cz.cvut.kbss.jopa.model.metamodel.AbstractPluralAttribute; +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.CollectionType; +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +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.model.metamodel.PropertiesSpecification; +import cz.cvut.kbss.jopa.model.metamodel.SingularAttribute; +import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; -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 java.net.URI; import java.util.Collection; @@ -34,6 +49,8 @@ */ abstract class FieldStrategy, X> { + protected static final Object LAZILY_LOADED_REFERENCE_PLACEHOLDER = new Object(); + final EntityType et; final T attribute; final Descriptor entityDescriptor; @@ -122,17 +139,13 @@ abstract class FieldStrategy, X> { private static FieldStrategy, Y> createPluralObjectPropertyStrategy( EntityType et, AbstractPluralAttribute attribute, Descriptor descriptor, EntityMappingHelper mapper) { - switch (attribute.getCollectionType()) { - case LIST: - return createListPropertyStrategy(et, (ListAttributeImpl) attribute, descriptor, - mapper); - case COLLECTION: - case SET: - return new SimpleSetPropertyStrategy<>(et, attribute, descriptor, mapper); - default: - throw new UnsupportedOperationException( - "Unsupported plural attribute collection type " + attribute.getCollectionType()); - } + return switch (attribute.getCollectionType()) { + case LIST -> createListPropertyStrategy(et, (ListAttributeImpl) attribute, descriptor, + mapper); + case COLLECTION, SET -> new SimpleSetPropertyStrategy<>(et, attribute, descriptor, mapper); + default -> throw new UnsupportedOperationException( + "Unsupported plural attribute collection type " + attribute.getCollectionType()); + }; } private static FieldStrategy, Y> createListPropertyStrategy( @@ -230,16 +243,39 @@ URI getAttributeWriteContext() { * * @param ax Axiom to extract value from */ - abstract void addValueFromAxiom(Axiom ax); + abstract void addAxiomValue(Axiom ax); + + /** + * Adds value from axiom in case the field is lazily loaded. + *

+ * This allows skipping loading of references of lazily loaded attributes while providing info whether there + * actually are any values to load eventually. + * + * @param ax Axiom to extract value from + */ + void lazilyAddAxiomValue(Axiom ax) { + addAxiomValue(ax); + } /** * Sets instance field from values gathered in this strategy. + *

+ * Note that if this strategy represents a plural field and there were no values added from axioms, the field value + * should be set to an empty instance of the collection/map corresponding to the target field type. * * @param instance The instance to receive the field value * @throws IllegalArgumentException Access error */ abstract void buildInstanceFieldValue(Object instance); + /** + * Checks whether any values have been added from axioms. + * + * @return {@code true} if this strategy holds values added from axioms, {@code false} otherwise + * @see #addAxiomValue(Axiom) + */ + abstract boolean hasValue(); + /** * Extracts values of field represented by this strategy from the specified instance and adds them to the specified * value gatherer. diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/IdentifierFieldStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/IdentifierFieldStrategy.java index c48d82855..f4e539b9f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/IdentifierFieldStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/IdentifierFieldStrategy.java @@ -35,10 +35,15 @@ class IdentifierFieldStrategy extends FieldStrategy, } @Override - void addValueFromAxiom(Axiom ax) { + void addAxiomValue(Axiom ax) { // Do nothing } + @Override + boolean hasValue() { + return false; + } + @Override void buildInstanceFieldValue(Object instance) { // Do nothing diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ListDescriptorFactory.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ListDescriptorFactory.java new file mode 100644 index 000000000..70c205a4c --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ListDescriptorFactory.java @@ -0,0 +1,62 @@ +package cz.cvut.kbss.jopa.oom; + +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.ListAttribute; +import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptorImpl; +import cz.cvut.kbss.ontodriver.descriptor.ReferencedListValueDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptorImpl; +import cz.cvut.kbss.ontodriver.descriptor.SimpleListValueDescriptor; +import cz.cvut.kbss.ontodriver.model.Assertion; +import cz.cvut.kbss.ontodriver.model.NamedResource; + +/** + * Creates descriptors for list attributes. + */ +class ListDescriptorFactory { + + static SimpleListDescriptor createSimpleListDescriptor(NamedResource owner, ListAttribute attribute) { + final boolean inferred = attribute.isInferred(); + final Assertion listProperty = Assertion.createObjectPropertyAssertion(attribute.getIRI().toURI(), inferred); + final Assertion nextNodeProperty = Assertion.createObjectPropertyAssertion(attribute.getHasNextPropertyIRI() + .toURI(), inferred); + return new SimpleListDescriptorImpl(owner, listProperty, nextNodeProperty); + } + + static SimpleListValueDescriptor createSimpleListValueDescriptor(NamedResource owner, + ListAttribute attribute) { + final boolean inferred = attribute.isInferred(); + final Assertion listProperty = Assertion.createObjectPropertyAssertion(attribute.getIRI().toURI(), inferred); + final Assertion nextNodeProperty = Assertion.createObjectPropertyAssertion(attribute.getHasNextPropertyIRI() + .toURI(), inferred); + return new SimpleListValueDescriptor(owner, listProperty, nextNodeProperty); + } + + static ReferencedListDescriptor createReferencedListDescriptor(NamedResource owner, ListAttribute attribute) { + final boolean inferred = attribute.isInferred(); + final Assertion listProperty = Assertion.createObjectPropertyAssertion(attribute.getIRI().toURI(), inferred); + final Assertion nextNodeProperty = Assertion + .createObjectPropertyAssertion(attribute.getHasNextPropertyIRI().toURI(), inferred); + final Assertion nodeContentProperty = nodeContentProperty(attribute); + return new ReferencedListDescriptorImpl(owner, listProperty, nextNodeProperty, nodeContentProperty, attribute.isRDFCollection()); + } + + private static Assertion nodeContentProperty(ListAttribute attribute) { + return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.OBJECT + ? Assertion.createObjectPropertyAssertion(attribute.getHasContentsPropertyIRI() + .toURI(), attribute.isInferred()) + : Assertion.createDataPropertyAssertion(attribute.getHasContentsPropertyIRI() + .toURI(), attribute.isInferred()); + } + + static ReferencedListValueDescriptor createReferencedListValueDescriptor(NamedResource owner, + ListAttribute attribute) { + final boolean inferred = attribute.isInferred(); + final Assertion listProperty = Assertion.createObjectPropertyAssertion(attribute.getIRI().toURI(), inferred); + final Assertion nextNodeProperty = Assertion + .createObjectPropertyAssertion(attribute.getHasNextPropertyIRI().toURI(), inferred); + final Assertion nodeContentProperty = nodeContentProperty(attribute); + return new ReferencedListValueDescriptor<>(owner, listProperty, nextNodeProperty, nodeContentProperty, attribute.isRDFCollection()); + } +} 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 1334f272a..014462eb3 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 @@ -20,8 +20,6 @@ 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.converter.ConverterWrapper; -import cz.cvut.kbss.jopa.oom.converter.DefaultConverterWrapper; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.jopa.utils.IdentifierTransformer; import cz.cvut.kbss.ontodriver.descriptor.ListDescriptor; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapper.java index fa99d389d..f7986ca22 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapper.java @@ -18,9 +18,10 @@ 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.FieldSpecification; -import cz.cvut.kbss.jopa.oom.exceptions.UnpersistedChangeException; -import cz.cvut.kbss.jopa.sessions.LoadingParameters; +import cz.cvut.kbss.jopa.oom.exception.UnpersistedChangeException; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.ontodriver.model.Axiom; import java.net.URI; @@ -67,6 +68,14 @@ public interface ObjectOntologyMapper { */ void loadFieldValue(T entity, FieldSpecification fieldSpec, Descriptor descriptor); + /** + * Generates a fresh identifier for an instance of the specified entity type. + * + * @param et Entity type used as base for the identifier + * @return New entity identifier + */ + URI generateIdentifier(EntityType et); + /** * Persists the specified entity into the underlying ontology. * 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 2e8efdb0a..83a984a5b 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 @@ -19,24 +19,47 @@ import cz.cvut.kbss.jopa.exceptions.StorageAccessException; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.model.metamodel.*; -import cz.cvut.kbss.jopa.oom.exceptions.EntityDeconstructionException; -import cz.cvut.kbss.jopa.oom.exceptions.EntityReconstructionException; -import cz.cvut.kbss.jopa.oom.exceptions.UnpersistedChangeException; -import cz.cvut.kbss.jopa.sessions.CacheManager; -import cz.cvut.kbss.jopa.sessions.LoadingParameters; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +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.QueryAttribute; +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.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.LoadingParameters; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.ontodriver.Connection; -import cz.cvut.kbss.ontodriver.descriptor.*; +import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.AxiomValueDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.ListValueDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.ReferencedListValueDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.SimpleListValueDescriptor; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; -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; import java.net.URI; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import static cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException.individualAlreadyManaged; @@ -44,8 +67,7 @@ public class ObjectOntologyMapperImpl implements ObjectOntologyMapper, EntityMap private static final Logger LOG = LoggerFactory.getLogger(ObjectOntologyMapperImpl.class); - private final UnitOfWorkImpl uow; - private final CacheManager cache; + private final AbstractUnitOfWork uow; private final Connection storageConnection; private final AxiomDescriptorFactory descriptorFactory; @@ -57,24 +79,29 @@ public class ObjectOntologyMapperImpl implements ObjectOntologyMapper, EntityMap private final EntityInstanceLoader defaultInstanceLoader; private final EntityInstanceLoader twoStepInstanceLoader; - public ObjectOntologyMapperImpl(UnitOfWorkImpl uow, Connection connection) { + public ObjectOntologyMapperImpl(AbstractUnitOfWork uow, Connection connection) { this.uow = Objects.requireNonNull(uow); - this.cache = uow.getLiveObjectCache(); this.storageConnection = Objects.requireNonNull(connection); this.descriptorFactory = new AxiomDescriptorFactory(); this.instanceRegistry = new HashMap<>(); this.pendingReferences = new PendingReferenceRegistry(); - this.entityBuilder = new EntityConstructor(this); + this.entityBuilder = new EntityConstructor(this, uow.getLoadStateRegistry()); this.entityBreaker = new EntityDeconstructor(this); this.defaultInstanceLoader = DefaultInstanceLoader.builder().connection(storageConnection) .metamodel(uow.getMetamodel()) .descriptorFactory(descriptorFactory) - .entityBuilder(entityBuilder).cache(cache).build(); + .entityBuilder(entityBuilder).cache(getCache()) + .loadStateRegistry(uow.getLoadStateRegistry()).build(); this.twoStepInstanceLoader = TwoStepInstanceLoader.builder().connection(storageConnection) .metamodel(uow.getMetamodel()) .descriptorFactory(descriptorFactory) - .entityBuilder(entityBuilder).cache(cache).build(); + .entityBuilder(entityBuilder).cache(getCache()) + .loadStateRegistry(uow.getLoadStateRegistry()).build(); + } + + private CacheManager getCache() { + return uow.getLiveObjectCache(); } @Override @@ -86,7 +113,7 @@ public boolean containsEntity(Class cls, URI identifier, Descriptor descr final EntityType et = getEntityType(cls); final NamedResource classUri = NamedResource.create(et.getIRI().toURI()); final Axiom ax = new AxiomImpl<>(NamedResource.create(identifier), - Assertion.createClassAssertion(false), new Value<>(classUri)); + Assertion.createClassAssertion(false), new Value<>(classUri)); try { return storageConnection.contains(ax, descriptor.getContexts()); } catch (OntoDriverException e) { @@ -103,7 +130,7 @@ public T loadEntity(LoadingParameters loadingParameters) { } private T loadEntityInternal(LoadingParameters loadingParameters) { - final IdentifiableEntityType et = getEntityType(loadingParameters.getEntityType()); + final IdentifiableEntityType et = getEntityType(loadingParameters.getEntityClass()); final T result; if (et.hasSubtypes()) { result = twoStepInstanceLoader.loadEntity(loadingParameters); @@ -111,7 +138,9 @@ private T loadEntityInternal(LoadingParameters loadingParameters) { result = defaultInstanceLoader.loadEntity(loadingParameters); } if (result != null) { - cache.add(loadingParameters.getIdentifier(), result, loadingParameters.getDescriptor()); + final LoadStateDescriptor loadStateDescriptor = uow.getLoadStateRegistry().get(result); + assert loadStateDescriptor != null; + getCache().add(loadingParameters.getIdentifier(), result, new Descriptors(loadingParameters.getDescriptor(), loadStateDescriptor)); } return result; } @@ -120,7 +149,7 @@ private T loadEntityInternal(LoadingParameters loadingParameters) { public T loadReference(LoadingParameters loadingParameters) { assert loadingParameters != null; - final IdentifiableEntityType et = getEntityType(loadingParameters.getEntityType()); + final IdentifiableEntityType et = getEntityType(loadingParameters.getEntityClass()); if (et.hasSubtypes()) { return twoStepInstanceLoader.loadReference(loadingParameters); } else { @@ -144,7 +173,7 @@ public void loadFieldValue(T entity, FieldSpecification fieldS assert fieldSpec != null; assert descriptor != null; - LOG.trace("Lazily loading value of field {} of entity {}.", fieldSpec, entity); + LOG.trace("Lazily loading value of field {} of entity {}.", fieldSpec, uow.stringify(entity)); final EntityType et = (EntityType) getEntityType(entity.getClass()); final URI primaryKey = EntityPropertiesUtils.getIdentifier(entity, et); @@ -169,16 +198,12 @@ public void loadFieldValue(T entity, FieldSpecification fieldS @Override public void persistEntity(URI identifier, T entity, Descriptor descriptor) { + assert identifier != null; assert entity != null; assert descriptor != null; @SuppressWarnings("unchecked") final EntityType et = (EntityType) getEntityType(entity.getClass()); try { - if (identifier == null) { - identifier = generateIdentifier(et); - assert identifier != null; - EntityPropertiesUtils.setIdentifier(identifier, entity, et); - } entityBreaker.setReferenceSavingResolver(new ReferenceSavingResolver(this)); final AxiomValueGatherer axiomBuilder = entityBreaker.mapEntityToAxioms(identifier, entity, et, descriptor); axiomBuilder.persist(storageConnection); @@ -230,7 +255,7 @@ public T getEntityFromCacheOrOntology(Class cls, URI identifier, Descript if (orig != null) { return orig; } - if (cache.contains(cls, identifier, descriptor)) { + if (getCache().contains(cls, identifier, descriptor)) { return defaultInstanceLoader.loadCached(getEntityType(cls), identifier, descriptor); } else if (instanceRegistry.containsKey(identifier)) { final Object existing = instanceRegistry.get(identifier); @@ -303,8 +328,7 @@ public void updateFieldValue(T entity, FieldSpecification fiel } private void removePendingAssertions(FieldSpecification fs, URI identifier) { - if (fs instanceof Attribute) { - final Attribute att = (Attribute) fs; + if (fs instanceof Attribute att) { // We care only about object property assertions, others are never pending final Assertion assertion = Assertion.createObjectPropertyAssertion(att.getIRI().toURI(), att.isInferred()); pendingReferences.removePendingReferences(NamedResource.create(identifier), assertion); @@ -345,7 +369,7 @@ public Set> getAttributeAxioms(T entity, FieldSpecification axiom, URI context) { try { return storageConnection.isInferred(axiom, context != null ? Collections.singleton(context) : - Collections.emptySet()); + Collections.emptySet()); } catch (OntoDriverException e) { throw new StorageAccessException(e); } @@ -368,7 +392,7 @@ public boolean isInferred(T entity, FieldSpecification fieldSp }).reduce(false, Boolean::logicalOr); } - public UnitOfWorkImpl getUow() { + public UnitOfWork getUow() { return uow; } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralAnnotationPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralAnnotationPropertyStrategy.java index 9ee061ad9..b0cd62b7e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralAnnotationPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralAnnotationPropertyStrategy.java @@ -43,7 +43,7 @@ class PluralAnnotationPropertyStrategy extends PluralDataPropertyStrategy } @Override - void addValueFromAxiom(Axiom ax) { + void addAxiomValue(Axiom ax) { final Object value = ax.getValue().getValue(); if (isValidRange(value)) { values.add(toAttributeValue(value)); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralDataPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralDataPropertyStrategy.java index d018998d2..55a3daeb7 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralDataPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralDataPropertyStrategy.java @@ -44,13 +44,18 @@ class PluralDataPropertyStrategy extends DataPropertyFieldStrategy ax) { + void addAxiomValue(Axiom ax) { final Object value = ax.getValue().getValue(); if (isValidRange(value)) { this.values.add(toAttributeValue(value)); } } + @Override + boolean hasValue() { + return !values.isEmpty(); + } + @Override boolean isValidRange(Object value) { return elementType.isAssignableFrom(value.getClass()) || canBeConverted(value); @@ -58,9 +63,7 @@ boolean isValidRange(Object value) { @Override void buildInstanceFieldValue(Object instance) { - if (!values.isEmpty()) { - setValueOnInstance(instance, values); - } + setValueOnInstance(instance, values); } @Override diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralMultilingualStringFieldStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralMultilingualStringFieldStrategy.java index 623f7d469..1bc980665 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralMultilingualStringFieldStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PluralMultilingualStringFieldStrategy.java @@ -23,7 +23,12 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.utils.CollectionFactory; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; -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.LangString; +import cz.cvut.kbss.ontodriver.model.NamedResource; +import cz.cvut.kbss.ontodriver.model.Value; import java.util.Collection; import java.util.Collections; @@ -45,11 +50,10 @@ public class PluralMultilingualStringFieldStrategy } @Override - void addValueFromAxiom(Axiom ax) { + void addAxiomValue(Axiom ax) { String value; String language = null; - if (ax.getValue().getValue() instanceof LangString) { - final LangString ls = (LangString) ax.getValue().getValue(); + if (ax.getValue().getValue() instanceof LangString ls) { value = ls.getValue(); language = ls.getLanguage().orElse(null); } else { @@ -58,6 +62,11 @@ void addValueFromAxiom(Axiom ax) { addValue(value, language); } + @Override + boolean hasValue() { + return !values.isEmpty(); + } + private void addValue(String value, String language) { for (MultilingualString ms : values) { if (!ms.contains(language)) { @@ -72,9 +81,7 @@ private void addValue(String value, String language) { @Override void buildInstanceFieldValue(Object instance) { - if (!values.isEmpty()) { - setValueOnInstance(instance, values); - } + setValueOnInstance(instance, values); } @Override 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 a2d75823d..4a77ad2f7 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 @@ -23,7 +23,11 @@ import cz.cvut.kbss.jopa.utils.CollectionFactory; 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; @@ -42,13 +46,15 @@ abstract class PluralObjectPropertyStrategy values; + private boolean hasLazyValues = false; + PluralObjectPropertyStrategy(EntityType et, Y att, Descriptor descriptor, EntityMappingHelper mapper) { super(et, att, descriptor, mapper); this.values = CollectionFactory.createDefaultCollection(att.getCollectionType()); } @Override - void addValueFromAxiom(Axiom ax) { + void addAxiomValue(Axiom ax) { final NamedResource valueIdentifier = (NamedResource) ax.getValue().getValue(); final Class elementType = attribute.getBindableJavaType(); if (IdentifierTransformer.isValidIdentifierType(elementType)) { @@ -61,8 +67,7 @@ void addValueFromAxiom(Axiom ax) { values.add(attribute.getConverter().convertToAttribute(valueIdentifier)); } else { final Object value = mapper.getEntityFromCacheOrOntology(elementType, valueIdentifier.getIdentifier(), - entityDescriptor.getAttributeDescriptor( - attribute)); + entityDescriptor.getAttributeDescriptor(attribute)); if (value != null) { values.add(value); } else { @@ -72,12 +77,25 @@ void addValueFromAxiom(Axiom ax) { } @Override - void buildInstanceFieldValue(Object instance) { - if (!values.isEmpty()) { - setValueOnInstance(instance, values); + void lazilyAddAxiomValue(Axiom ax) { + final Class elementType = attribute.getBindableJavaType(); + if (IdentifierTransformer.isValidIdentifierType(elementType) || elementType.isEnum()) { + addAxiomValue(ax); + } else { + hasLazyValues = true; } } + @Override + boolean hasValue() { + return !values.isEmpty() || hasLazyValues; + } + + @Override + void buildInstanceFieldValue(Object instance) { + setValueOnInstance(instance, values); + } + @Override Set> buildAxiomsFromInstance(X instance) { final Object value = extractFieldValueFromInstance(instance); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PropertiesFieldStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PropertiesFieldStrategy.java index fa9460692..0603b62ee 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PropertiesFieldStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/PropertiesFieldStrategy.java @@ -43,7 +43,7 @@ class PropertiesFieldStrategy extends FieldStrategy ax) { + void addAxiomValue(Axiom ax) { if (shouldSkipAxiom(ax)) { return; } @@ -68,11 +68,13 @@ private boolean isMappedAttribute(Axiom ax) { return false; } + @Override + boolean hasValue() { + return !value.map.isEmpty(); + } + @Override void buildInstanceFieldValue(Object instance) { - if (value.getValue().isEmpty()) { - return; - } setValueOnInstance(instance, value.getValue()); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ReferencedListDataPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ReferencedListDataPropertyStrategy.java index 5bbbe1e7e..f06fb6832 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ReferencedListDataPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ReferencedListDataPropertyStrategy.java @@ -5,13 +5,10 @@ import cz.cvut.kbss.jopa.model.metamodel.ListAttributeImpl; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; -import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptorImpl; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListValueDescriptor; -import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; -import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -31,12 +28,12 @@ class ReferencedListDataPropertyStrategy extends DataPropertyFieldStrategy

  • head) { + void addAxiomValue(Axiom head) { final ReferencedListDescriptor listDescriptor = createListDescriptor(head); final Collection> sequence = mapper.loadReferencedList(listDescriptor); sequence.stream() .filter(item -> item.getAssertion().getIdentifier() - .equals(attribute.getOWLPropertyHasContentsIRI().toURI())) + .equals(attribute.getHasContentsPropertyIRI().toURI())) .forEach(item -> { final Object value = item.getValue().getValue(); if (isValidRange(value)) { @@ -47,19 +44,16 @@ void addValueFromAxiom(Axiom head) { ReferencedListDescriptor createListDescriptor(Axiom ax) { final NamedResource owner = ax.getSubject(); - - final boolean inferred = attribute.isInferred(); - final Assertion listProperty = Assertion.createObjectPropertyAssertion(attribute.getIRI().toURI(), inferred); - final Assertion nextNodeProperty = Assertion - .createObjectPropertyAssertion(attribute.getOWLObjectPropertyHasNextIRI().toURI(), inferred); - final Assertion nodeContentProperty = Assertion.createDataPropertyAssertion(attribute.getOWLPropertyHasContentsIRI() - .toURI(), inferred); - final ReferencedListDescriptor listDescriptor = new ReferencedListDescriptorImpl(owner, listProperty, - nextNodeProperty, nodeContentProperty); + final ReferencedListDescriptor listDescriptor = ListDescriptorFactory.createReferencedListDescriptor(owner, attribute); listDescriptor.setContext(getAttributeWriteContext()); return listDescriptor; } + @Override + boolean hasValue() { + return !values.isEmpty(); + } + @Override boolean isValidRange(Object value) { return elementType.isAssignableFrom(value.getClass()) || canBeConverted(value); @@ -67,9 +61,7 @@ boolean isValidRange(Object value) { @Override void buildInstanceFieldValue(Object instance) { - if (!values.isEmpty()) { - setValueOnInstance(instance, values); - } + setValueOnInstance(instance, values); } @Override @@ -87,16 +79,8 @@ void buildAxiomValuesFromInstance(X instance, AxiomValueGatherer valueBuilder) { } private ReferencedListValueDescriptor createListValueDescriptor(X instance) { - final URI owner = EntityPropertiesUtils.getIdentifier(instance, et); - final boolean inferred = attribute.isInferred(); - final Assertion hasList = Assertion - .createObjectPropertyAssertion(attribute.getIRI().toURI(), inferred); - final Assertion hasNext = Assertion.createObjectPropertyAssertion(attribute - .getOWLObjectPropertyHasNextIRI().toURI(), inferred); - final Assertion hasContent = Assertion.createDataPropertyAssertion(attribute.getOWLPropertyHasContentsIRI() - .toURI(), inferred); - final ReferencedListValueDescriptor descriptor = new ReferencedListValueDescriptor<>( - NamedResource.create(owner), hasList, hasNext, hasContent); + final NamedResource owner = NamedResource.create(EntityPropertiesUtils.getIdentifier(instance, et)); + final ReferencedListValueDescriptor descriptor = ListDescriptorFactory.createReferencedListValueDescriptor(owner, attribute); descriptor.setContext(getAttributeWriteContext()); return descriptor; } 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 ee83bc72c..6dc58645e 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 @@ -22,13 +22,10 @@ import cz.cvut.kbss.jopa.model.metamodel.ListAttributeImpl; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; -import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptorImpl; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListValueDescriptor; -import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; -import java.net.URI; import java.util.Collection; import java.util.List; @@ -41,25 +38,17 @@ class ReferencedListPropertyStrategy extends } @Override - void addValueFromAxiom(Axiom ax) { + void addAxiomValue(Axiom ax) { final ReferencedListDescriptor listDescriptor = createListDescriptor(ax); final Collection> sequence = mapper.loadReferencedList(listDescriptor); sequence.stream() - .filter(a -> a.getAssertion().getIdentifier().equals(attribute.getOWLPropertyHasContentsIRI().toURI())) - .forEach(super::addValueFromAxiom); + .filter(a -> a.getAssertion().getIdentifier().equals(attribute.getHasContentsPropertyIRI().toURI())) + .forEach(super::addAxiomValue); } ReferencedListDescriptor createListDescriptor(Axiom ax) { final NamedResource owner = ax.getSubject(); - - final boolean inferred = attribute.isInferred(); - final Assertion listProperty = Assertion.createObjectPropertyAssertion(attribute.getIRI().toURI(), inferred); - final Assertion nextNodeProperty = Assertion - .createObjectPropertyAssertion(attribute.getOWLObjectPropertyHasNextIRI().toURI(), inferred); - final Assertion nodeContentProperty = Assertion.createObjectPropertyAssertion(attribute.getOWLPropertyHasContentsIRI() - .toURI(), inferred); - final ReferencedListDescriptor listDescriptor = new ReferencedListDescriptorImpl(owner, listProperty, - nextNodeProperty, nodeContentProperty); + final ReferencedListDescriptor listDescriptor = ListDescriptorFactory.createReferencedListDescriptor(owner, attribute); listDescriptor.setContext(getAttributeWriteContext()); return listDescriptor; } @@ -77,16 +66,8 @@ void extractListValues(List list, X instance, AxiomValueGatherer valueBui } ReferencedListValueDescriptor createListValueDescriptor(X instance) { - final URI owner = EntityPropertiesUtils.getIdentifier(instance, et); - final boolean inferred = attribute.isInferred(); - final Assertion hasList = Assertion - .createObjectPropertyAssertion(attribute.getIRI().toURI(), inferred); - final Assertion hasNext = Assertion.createObjectPropertyAssertion(attribute - .getOWLObjectPropertyHasNextIRI().toURI(), inferred); - final Assertion hasContent = Assertion.createObjectPropertyAssertion(attribute.getOWLPropertyHasContentsIRI() - .toURI(), inferred); - final ReferencedListValueDescriptor descriptor = new ReferencedListValueDescriptor<>( - NamedResource.create(owner), hasList, hasNext, hasContent); + final NamedResource owner = NamedResource.create(EntityPropertiesUtils.getIdentifier(instance, et)); + final ReferencedListValueDescriptor descriptor = ListDescriptorFactory.createReferencedListValueDescriptor(owner, attribute); descriptor.setContext(getAttributeWriteContext()); return descriptor; } 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 a53e539df..7fbfc7f6e 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 @@ -21,9 +21,7 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.ListAttributeImpl; import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptor; -import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptorImpl; import cz.cvut.kbss.ontodriver.descriptor.SimpleListValueDescriptor; -import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; @@ -39,20 +37,15 @@ class SimpleListPropertyStrategy extends } @Override - void addValueFromAxiom(Axiom ax) { + void addAxiomValue(Axiom ax) { final SimpleListDescriptor listDescriptor = createListDescriptor(ax); final Collection> sequence = mapper.loadSimpleList(listDescriptor); - sequence.forEach(super::addValueFromAxiom); + sequence.forEach(super::addAxiomValue); } SimpleListDescriptor createListDescriptor(Axiom ax) { final NamedResource owner = ax.getSubject(); - final Assertion listProperty = Assertion - .createObjectPropertyAssertion(attribute.getIRI().toURI(), attribute.isInferred()); - final Assertion nextNodeProperty = Assertion - .createObjectPropertyAssertion(attribute.getOWLObjectPropertyHasNextIRI().toURI(), - attribute.isInferred()); - final SimpleListDescriptor listDescriptor = new SimpleListDescriptorImpl(owner, listProperty, nextNodeProperty); + final SimpleListDescriptor listDescriptor = ListDescriptorFactory.createSimpleListDescriptor(owner, attribute); listDescriptor.setContext(getAttributeWriteContext()); return listDescriptor; } @@ -71,12 +64,7 @@ void extractListValues(List list, X instance, AxiomValueGatherer valueBui SimpleListValueDescriptor createListValueDescriptor(X instance) { final NamedResource owner = NamedResource.create(resolveValueIdentifier(instance, et)); - final Assertion listProperty = Assertion - .createObjectPropertyAssertion(attribute.getIRI().toURI(), attribute.isInferred()); - final Assertion nextNodeProperty = Assertion.createObjectPropertyAssertion(attribute - .getOWLObjectPropertyHasNextIRI().toURI(), attribute.isInferred()); - final SimpleListValueDescriptor listDescriptor = new SimpleListValueDescriptor(owner, listProperty, - nextNodeProperty); + final SimpleListValueDescriptor listDescriptor = ListDescriptorFactory.createSimpleListValueDescriptor(owner, attribute); listDescriptor.setContext(getAttributeWriteContext()); return listDescriptor; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularAnnotationPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularAnnotationPropertyStrategy.java index 762b986ef..b1f04f413 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularAnnotationPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularAnnotationPropertyStrategy.java @@ -40,7 +40,7 @@ class SingularAnnotationPropertyStrategy extends SingularDataPropertyStrategy } @Override - void addValueFromAxiom(Axiom ax) { + void addAxiomValue(Axiom ax) { final Object val = ax.getValue().getValue(); if (!isValidRange(val)) { return; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularDataPropertyStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularDataPropertyStrategy.java index 52a2e83ad..7f8fbe0a7 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularDataPropertyStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularDataPropertyStrategy.java @@ -40,7 +40,7 @@ class SingularDataPropertyStrategy extends DataPropertyFieldStrategy ax) { + void addAxiomValue(Axiom ax) { final Object val = ax.getValue().getValue(); if (!isValidRange(val)) { return; @@ -57,6 +57,11 @@ void verifyCardinalityConstraint(NamedResource subject) { } } + @Override + boolean hasValue() { + return value != null; + } + @Override void buildInstanceFieldValue(Object entity) { setValueOnInstance(entity, value); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularMultilingualStringFieldStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularMultilingualStringFieldStrategy.java index b1fe615cf..1c09f0c1e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularMultilingualStringFieldStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/SingularMultilingualStringFieldStrategy.java @@ -42,13 +42,12 @@ class SingularMultilingualStringFieldStrategy } @Override - void addValueFromAxiom(Axiom ax) { + void addAxiomValue(Axiom ax) { if (value == null) { this.value = new MultilingualString(); } final Value axiomValue = ax.getValue(); - if (axiomValue.getValue() instanceof LangString) { - final LangString lsAxiomValue = (LangString) axiomValue.getValue(); + if (axiomValue.getValue() instanceof LangString lsAxiomValue) { verifyCardinality(lsAxiomValue.getLanguage().orElse(null), ax.getSubject()); value.set(lsAxiomValue.getLanguage().orElse(null), lsAxiomValue.getValue()); } else { @@ -68,6 +67,11 @@ private void verifyCardinality(String language, NamedResource subject) { } } + @Override + boolean hasValue() { + return value != null; + } + @Override void buildInstanceFieldValue(Object instance) { setValueOnInstance(instance, value); 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 e59a03fa2..2bc6a3cc3 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 @@ -44,7 +44,7 @@ class SingularObjectPropertyStrategy extends FieldStrategy ax) { + void addAxiomValue(Axiom ax) { assert ax.getValue().getValue() instanceof NamedResource; final NamedResource valueIdentifier = (NamedResource) ax.getValue().getValue(); final Class targetType = attribute.getJavaType(); @@ -77,8 +77,26 @@ private void verifyCardinality(NamedResource subject) { } } + @Override + void lazilyAddAxiomValue(Axiom ax) { + final Class targetType = attribute.getJavaType(); + if (IdentifierTransformer.isValidIdentifierType(targetType) || targetType.isEnum()) { + addAxiomValue(ax); + } else { + this.value = LAZILY_LOADED_REFERENCE_PLACEHOLDER; + } + } + + @Override + boolean hasValue() { + return value != null; + } + @Override void buildInstanceFieldValue(Object instance) { + if (value == LAZILY_LOADED_REFERENCE_PLACEHOLDER) { + return; + } setValueOnInstance(instance, value); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/TwoStepInstanceLoader.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/TwoStepInstanceLoader.java index badfcfcea..7d7776892 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/TwoStepInstanceLoader.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/TwoStepInstanceLoader.java @@ -19,11 +19,10 @@ import cz.cvut.kbss.jopa.exception.InstantiationException; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; -import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; -import cz.cvut.kbss.jopa.oom.exceptions.EntityReconstructionException; +import cz.cvut.kbss.jopa.oom.exception.EntityReconstructionException; import cz.cvut.kbss.jopa.oom.metamodel.PolymorphicEntityTypeResolver; -import cz.cvut.kbss.jopa.sessions.LoadingParameters; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; @@ -39,9 +38,9 @@ private TwoStepInstanceLoader(TwoStepInstanceLoaderBuilder builder) { @Override T loadEntity(LoadingParameters loadingParameters) { - final IdentifiableEntityType rootEt = metamodel.entity(loadingParameters.getEntityType()); + final IdentifiableEntityType rootEt = metamodel.entity(loadingParameters.getEntityClass()); try { - final EntityType et = resolveEntityType(loadingParameters, rootEt); + final IdentifiableEntityType et = resolveEntityType(loadingParameters, rootEt); if (et == null) { return null; } @@ -53,9 +52,9 @@ T loadEntity(LoadingParameters loadingParameters) { @Override T loadReference(LoadingParameters loadingParameters) { - final IdentifiableEntityType rootEt = metamodel.entity(loadingParameters.getEntityType()); + final IdentifiableEntityType rootEt = metamodel.entity(loadingParameters.getEntityClass()); try { - final EntityType et = resolveEntityType(loadingParameters, rootEt); + final IdentifiableEntityType et = resolveEntityType(loadingParameters, rootEt); return et != null ? entityBuilder.createEntityInstance(loadingParameters.getIdentifier(), et) : null; } catch (OntoDriverException e) { throw new StorageAccessException(e); @@ -64,7 +63,7 @@ T loadReference(LoadingParameters loadingParameters) { } } - private EntityType resolveEntityType(LoadingParameters loadingParameters, + private IdentifiableEntityType resolveEntityType(LoadingParameters loadingParameters, IdentifiableEntityType rootEt) throws OntoDriverException { NamedResource individual = NamedResource.create(loadingParameters.getIdentifier()); final Set> types = storageConnection.types().getTypes(individual, diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/TypesFieldStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/TypesFieldStrategy.java index 9c0cc8300..b1ae8998e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/TypesFieldStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/TypesFieldStrategy.java @@ -41,7 +41,7 @@ class TypesFieldStrategy extends FieldStrategy ax) { + void addAxiomValue(Axiom ax) { if (MappingUtils.isEntityClassAssertion(ax, et)) { return; } @@ -50,13 +50,14 @@ void addValueFromAxiom(Axiom ax) { values.add(type); } + @Override + boolean hasValue() { + return !values.isEmpty(); + } + @Override void buildInstanceFieldValue(Object instance) { assert attribute.getJavaField().getType().isAssignableFrom(Set.class); - - if (values.isEmpty()) { - return; - } setValueOnInstance(instance, values); } @@ -97,7 +98,7 @@ private void extractTypesToAdd(AxiomValueGatherer valueBuilder, Set types, Se private static Set typesDiff(Set base, Set difference) { final Set addedDiff = new HashSet<>(base.size()); addedDiff.addAll(difference.stream().filter(t -> !base.contains(t)).map(t -> URI.create(t.toString())) - .collect(Collectors.toList())); + .toList()); return addedDiff; } @@ -108,7 +109,7 @@ private void extractTypesToRemove(AxiomValueGatherer valueBuilder, Set types, private static Set prepareTypes(Set types) { final Set toAdd = new HashSet<>(types.size()); - toAdd.addAll(types.stream().map(t -> URI.create(t.toString())).collect(Collectors.toList())); + toAdd.addAll(types.stream().map(t -> URI.create(t.toString())).toList()); return toAdd; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/AmbiguousEntityTypeException.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/AmbiguousEntityTypeException.java similarity index 96% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/AmbiguousEntityTypeException.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/AmbiguousEntityTypeException.java index dfab42d0e..5f3a58636 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/AmbiguousEntityTypeException.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/AmbiguousEntityTypeException.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.oom.exceptions; +package cz.cvut.kbss.jopa.oom.exception; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/EntityDeconstructionException.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/EntityDeconstructionException.java similarity index 96% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/EntityDeconstructionException.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/EntityDeconstructionException.java index 0bdc2a86b..694b9ca7d 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/EntityDeconstructionException.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/EntityDeconstructionException.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.oom.exceptions; +package cz.cvut.kbss.jopa.oom.exception; /** * Indicates and error during the process of mapping an entity to axioms. diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/EntityReconstructionException.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/EntityReconstructionException.java similarity index 96% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/EntityReconstructionException.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/EntityReconstructionException.java index 68c9595ca..b71a61ec3 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/EntityReconstructionException.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/EntityReconstructionException.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.oom.exceptions; +package cz.cvut.kbss.jopa.oom.exception; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/UnpersistedChangeException.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/UnpersistedChangeException.java similarity index 96% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/UnpersistedChangeException.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/UnpersistedChangeException.java index 829654a16..ff2f67333 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exceptions/UnpersistedChangeException.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/exception/UnpersistedChangeException.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.oom.exceptions; +package cz.cvut.kbss.jopa.oom.exception; /** * Thrown when an unpersisted change is found on commit. diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/metamodel/PolymorphicEntityTypeResolver.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/metamodel/PolymorphicEntityTypeResolver.java index 6e7c65459..cfb4d8d53 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/metamodel/PolymorphicEntityTypeResolver.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/metamodel/PolymorphicEntityTypeResolver.java @@ -21,7 +21,7 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; import cz.cvut.kbss.jopa.model.metamodel.Type; -import cz.cvut.kbss.jopa.oom.exceptions.AmbiguousEntityTypeException; +import cz.cvut.kbss.jopa.oom.exception.AmbiguousEntityTypeException; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; @@ -37,7 +37,7 @@ public class PolymorphicEntityTypeResolver { private final Set types; private final IdentifiableEntityType root; - private final Set> matches = new HashSet<>(2); + private final Set> matches = new HashSet<>(2); public PolymorphicEntityTypeResolver(NamedResource individual, IdentifiableEntityType root, Collection> typeAxioms) { @@ -58,7 +58,7 @@ public PolymorphicEntityTypeResolver(NamedResource individual, IdentifiableEntit * @return The specified root entity type or the most specific non-abstract unique entity type * @throws AmbiguousEntityTypeException When multiple entity types match the specified types */ - public EntityType determineActualEntityType() { + public IdentifiableEntityType determineActualEntityType() { if (types.contains(root.getIRI().toURI()) && !root.isAbstract()) { return root; } @@ -85,6 +85,7 @@ private void findMatchingEntityType(AbstractIdentifiableType parent for (AbstractIdentifiableType subtype : parent.getSubtypes()) { final Set> updatedAncestors = new HashSet<>(ancestors); if (subtype.getPersistenceType() == Type.PersistenceType.ENTITY && !subtype.isAbstract()) { + assert subtype instanceof IdentifiableEntityType; final IdentifiableEntityType et = (IdentifiableEntityType) subtype; final URI etUri = et.getIRI().toURI(); if (types.contains(etUri)) { @@ -96,7 +97,7 @@ private void findMatchingEntityType(AbstractIdentifiableType parent } } - private void addMatchingType(EntityType et, Set> ancestors) { + private void addMatchingType(IdentifiableEntityType et, Set> ancestors) { matches.add(et); ancestors.forEach(matches::remove); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectWrapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/IndirectWrapper.java similarity index 88% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectWrapper.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/IndirectWrapper.java index 686aa892a..c9b6b65b6 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectWrapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/IndirectWrapper.java @@ -15,19 +15,21 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.adapters; +package cz.cvut.kbss.jopa.proxy; /** * Wraps a target object in an indirect proxy which is able to intercept method calls and perform additional processing * (usually persistence context notification). + * + * @param Type of the wrapped object */ @FunctionalInterface -public interface IndirectWrapper { +public interface IndirectWrapper { /** * Retrieves the wrapped object. * * @return The wrapped object */ - Object unwrap(); + T unwrap(); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectCollection.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectCollection.java similarity index 72% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectCollection.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectCollection.java index d42d3fcf7..4edb73f6d 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectCollection.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectCollection.java @@ -15,27 +15,24 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.adapters; +package cz.cvut.kbss.jopa.proxy.change; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.proxy.IndirectWrapper; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import java.lang.reflect.Field; /** - * Wraps a collection so that calls to modifying operations are intercepted and reported to the persistence context (if necessary). + * Wraps a collection so that calls to modifying operations are intercepted and reported to the persistence context (if + * necessary). + * * @param Type of the wrapped object */ -public abstract class IndirectCollection implements IndirectWrapper { +public abstract class ChangeTrackingIndirectCollection implements IndirectWrapper { protected final transient Object owner; protected final transient Field field; - protected final transient UnitOfWorkImpl persistenceContext; - - protected IndirectCollection() { - owner = null; - field = null; - persistenceContext = null; - } + protected final transient UnitOfWork persistenceContext; /** * Create new indirect collection from the specified data. @@ -47,7 +44,7 @@ protected IndirectCollection() { * @param persistenceContext Persistence context the owner belongs to * @throws NullPointerException If the persistence context is null */ - protected IndirectCollection(Object owner, Field f, UnitOfWorkImpl persistenceContext) { + protected ChangeTrackingIndirectCollection(Object owner, Field f, UnitOfWork persistenceContext) { if (persistenceContext == null) { throw new NullPointerException("Null passed in as persistenceContext."); } @@ -62,12 +59,4 @@ protected void persistChange() { persistenceContext.attributeChanged(owner, field); } } - - /** - * The returned type is determined by the instance type parameter. - * - * @return The collection wrapped in this indirect collection - */ - @Override - public abstract T unwrap(); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectList.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectList.java similarity index 85% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectList.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectList.java index d30acc5b3..c9c20fdc1 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectList.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectList.java @@ -15,24 +15,21 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.adapters; +package cz.cvut.kbss.jopa.proxy.change; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import java.lang.reflect.Field; -import java.util.*; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; -public class IndirectList extends IndirectCollection> implements List { +public class ChangeTrackingIndirectList extends ChangeTrackingIndirectCollection> implements List { private final List internalList; - /** - * No-arg constructor to allow clone building. - */ - IndirectList() { - this.internalList = new ArrayList<>(); - } - /** * Create new indirect list backed by the specified referenced list. * @@ -42,7 +39,7 @@ public class IndirectList extends IndirectCollection> implements List * @param referencedList The list to reference * @throws NullPointerException If the {@code referencedList} is null */ - public IndirectList(Object owner, Field f, UnitOfWorkImpl uow, List referencedList) { + public ChangeTrackingIndirectList(Object owner, Field f, UnitOfWork uow, List referencedList) { super(owner, f, uow); this.internalList = Objects.requireNonNull(referencedList); } @@ -62,11 +59,9 @@ public void add(int arg0, E arg1) { @Override public boolean addAll(Collection c) { - boolean res = internalList.addAll(c); - if (res) { - persistChange(); - } - return res; + internalList.addAll(c); + persistChange(); + return true; } @Override @@ -177,7 +172,7 @@ public int size() { @Override public List subList(int fromIndex, int toIndex) { - return new IndirectList<>(owner, field, persistenceContext, internalList.subList(fromIndex, toIndex)); + return new ChangeTrackingIndirectList<>(owner, field, persistenceContext, internalList.subList(fromIndex, toIndex)); } @Override @@ -198,8 +193,8 @@ public List unwrap() { @Override public boolean equals(Object o) { if (o instanceof List) { - if (o instanceof IndirectList) { - return internalList.equals(((IndirectList) o).internalList); + if (o instanceof ChangeTrackingIndirectList) { + return internalList.equals(((ChangeTrackingIndirectList) o).internalList); } return internalList.equals(o); } @@ -237,7 +232,7 @@ public E next() { @Override public void remove() { it.remove(); - IndirectList.this.persistChange(); + ChangeTrackingIndirectList.this.persistChange(); } } @@ -282,19 +277,19 @@ public int previousIndex() { @Override public void remove() { lit.remove(); - IndirectList.this.persistChange(); + ChangeTrackingIndirectList.this.persistChange(); } @Override public void set(E e) { lit.set(e); - IndirectList.this.persistChange(); + ChangeTrackingIndirectList.this.persistChange(); } @Override public void add(E e) { lit.add(e); - IndirectList.this.persistChange(); + ChangeTrackingIndirectList.this.persistChange(); } } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectMap.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMap.java similarity index 82% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectMap.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMap.java index b905f5cf9..8de6a2bad 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectMap.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMap.java @@ -15,25 +15,21 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.adapters; +package cz.cvut.kbss.jopa.proxy.change; -import java.lang.reflect.Field; -import java.util.*; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Set; -public class IndirectMap extends IndirectCollection> implements Map { +public class ChangeTrackingIndirectMap extends ChangeTrackingIndirectCollection> implements Map { private final Map internalMap; - /** - * No-arg constructor to support clone building - */ - IndirectMap() { - this.internalMap = new HashMap<>(); - } - - public IndirectMap(Object owner, Field f, UnitOfWorkImpl persistenceContext, Map referencedMap) { + public ChangeTrackingIndirectMap(Object owner, Field f, UnitOfWork persistenceContext, Map referencedMap) { super(owner, f, persistenceContext); this.internalMap = Objects.requireNonNull(referencedMap); } @@ -118,8 +114,8 @@ public Set> entrySet() { @Override public boolean equals(Object o) { if (o instanceof Map) { - if (o instanceof IndirectMap) { - return internalMap.equals(((IndirectMap) o).internalMap); + if (o instanceof ChangeTrackingIndirectMap) { + return internalMap.equals(((ChangeTrackingIndirectMap) o).internalMap); } return internalMap.equals(o); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectMultilingualString.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMultilingualString.java similarity index 83% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectMultilingualString.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMultilingualString.java index 62ec5aadb..086d907d4 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectMultilingualString.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMultilingualString.java @@ -15,10 +15,11 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.adapters; +package cz.cvut.kbss.jopa.proxy.change; +import cz.cvut.kbss.jopa.proxy.IndirectWrapper; import cz.cvut.kbss.jopa.model.MultilingualString; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import java.lang.reflect.Field; import java.util.Map; @@ -29,21 +30,14 @@ * Wraps a {@link MultilingualString} so that calls to modifying operations are intercepted and reported to the * persistence context (if necessary). */ -public class IndirectMultilingualString extends MultilingualString implements IndirectWrapper { +public class ChangeTrackingIndirectMultilingualString extends MultilingualString implements IndirectWrapper { private final transient Object owner; private final transient Field field; - private final transient UnitOfWorkImpl persistenceContext; + private final transient UnitOfWork persistenceContext; private final MultilingualString referencedString; - protected IndirectMultilingualString() { - this.owner = null; - this.field = null; - this.persistenceContext = null; - this.referencedString = new MultilingualString(); - } - /** * Create new indirect multilingual string backed by the specified referenced {@link MultilingualString}. * @@ -53,7 +47,7 @@ protected IndirectMultilingualString() { * @param referencedString The string to reference * @throws NullPointerException If the {@code referencedString} is null */ - public IndirectMultilingualString(Object owner, Field f, UnitOfWorkImpl uow, MultilingualString referencedString) { + public ChangeTrackingIndirectMultilingualString(Object owner, Field f, UnitOfWork uow, MultilingualString referencedString) { this.owner = owner; this.field = f; this.persistenceContext = Objects.requireNonNull(uow); @@ -125,8 +119,8 @@ public Map getValue() { @Override public boolean equals(Object o) { if (o instanceof MultilingualString) { - if (o instanceof IndirectMultilingualString) { - return referencedString.equals(((IndirectMultilingualString) o).referencedString); + if (o instanceof ChangeTrackingIndirectMultilingualString) { + return referencedString.equals(((ChangeTrackingIndirectMultilingualString) o).referencedString); } return referencedString.equals(o); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectSet.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectSet.java similarity index 85% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectSet.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectSet.java index 84dd63ef8..b5df8e075 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/adapters/IndirectSet.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectSet.java @@ -15,25 +15,21 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.adapters; +package cz.cvut.kbss.jopa.proxy.change; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import java.lang.reflect.Field; -import java.util.*; +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; -public class IndirectSet extends IndirectCollection> implements Set { +public class ChangeTrackingIndirectSet extends ChangeTrackingIndirectCollection> implements Set { private final Set internalSet; - /** - * No-arg constructor to allow clone building. - */ - IndirectSet() { - this.internalSet = new HashSet<>(); - } - - public IndirectSet(Object owner, Field f, UnitOfWorkImpl uow, Set referencedSet) { + public ChangeTrackingIndirectSet(Object owner, Field f, UnitOfWork uow, Set referencedSet) { super(owner, f, uow); this.internalSet = Objects.requireNonNull(referencedSet); } @@ -146,7 +142,7 @@ public T next() { @Override public void remove() { iterator.remove(); - IndirectSet.this.persistChange(); + ChangeTrackingIndirectSet.this.persistChange(); } } @@ -158,8 +154,8 @@ public Set unwrap() { @Override public boolean equals(Object o) { if (o instanceof Set) { - if (o instanceof IndirectSet) { - return internalSet.equals(((IndirectSet) o).internalSet); + if (o instanceof ChangeTrackingIndirectSet) { + return internalSet.equals(((ChangeTrackingIndirectSet) o).internalSet); } return internalSet.equals(o); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingCollectionProxy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingCollectionProxy.java new file mode 100644 index 000000000..4f99b9e47 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingCollectionProxy.java @@ -0,0 +1,126 @@ +package cz.cvut.kbss.jopa.proxy.lazy; + +import cz.cvut.kbss.jopa.exception.LazyLoadingException; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; + +import java.util.Collection; +import java.util.Iterator; + +/** + * Collection proxy that triggers field lazy loading when accessed (and connected to an active persistence context). + * + * @param Owner object type + * @param Wrapped object type + */ +abstract class LazyLoadingCollectionProxy, E> implements LazyLoadingProxy, Collection { + + protected final transient O owner; + protected final transient FieldSpecification fieldSpec; + protected final transient UnitOfWork persistenceContext; + + private transient T value; + + public LazyLoadingCollectionProxy(O owner, FieldSpecification fieldSpec, + UnitOfWork persistenceContext) { + this.owner = owner; + this.fieldSpec = fieldSpec; + this.persistenceContext = persistenceContext; + } + + @Override + public T triggerLazyLoading() { + if (value != null) { + return value; + } + if (persistenceContext == null || !persistenceContext.isActive()) { + throw new LazyLoadingException("No active persistence context is available in lazy loading proxy for attribute " + + fieldSpec + " of entity " + owner); + } + this.value = (T) persistenceContext.loadEntityField(owner, fieldSpec); + return value; + } + + @Override + public boolean isLoaded() { + return value != null; + } + + @Override + public T getLoadedValue() { + if (value == null) { + throw new IllegalStateException("Proxy has not been loaded, yet."); + } + return value; + } + + @Override + public int size() { + return triggerLazyLoading().size(); + } + + @Override + public boolean isEmpty() { + return triggerLazyLoading().isEmpty(); + } + + @Override + public boolean contains(Object o) { + return triggerLazyLoading().contains(o); + } + + @Override + public Iterator iterator() { + return triggerLazyLoading().iterator(); + } + + @Override + public Object[] toArray() { + return triggerLazyLoading().toArray(); + } + + @Override + public ET[] toArray(ET[] a) { + return triggerLazyLoading().toArray(a); + } + + @Override + public boolean add(E e) { + return triggerLazyLoading().add(e); + } + + @Override + public boolean remove(Object o) { + return triggerLazyLoading().remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return triggerLazyLoading().containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return triggerLazyLoading().addAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return triggerLazyLoading().removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return triggerLazyLoading().retainAll(c); + } + + @Override + public void clear() { + triggerLazyLoading().clear(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + owner.getClass().getSimpleName() + "." + fieldSpec.getName() + "]"; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingListProxy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingListProxy.java new file mode 100644 index 000000000..6eddc290b --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingListProxy.java @@ -0,0 +1,73 @@ +package cz.cvut.kbss.jopa.proxy.lazy; + +import cz.cvut.kbss.jopa.model.metamodel.CollectionType; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.utils.CollectionFactory; + +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; + +public class LazyLoadingListProxy extends LazyLoadingCollectionProxy, E> implements List { + + public LazyLoadingListProxy(O owner, FieldSpecification> fieldSpec, + UnitOfWork persistenceContext) { + super(owner, fieldSpec, persistenceContext); + } + + @Override + public List unwrap() { + return (List) CollectionFactory.createDefaultCollection(CollectionType.LIST); + } + + @Override + public boolean addAll(int index, Collection c) { + return triggerLazyLoading().addAll(index, c); + } + + @Override + public E get(int index) { + return triggerLazyLoading().get(index); + } + + @Override + public E set(int index, E element) { + return triggerLazyLoading().set(index, element); + } + + @Override + public void add(int index, E element) { + triggerLazyLoading().add(index, element); + } + + @Override + public E remove(int index) { + return triggerLazyLoading().remove(index); + } + + @Override + public int indexOf(Object o) { + return triggerLazyLoading().indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return triggerLazyLoading().lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return triggerLazyLoading().listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return triggerLazyLoading().listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return triggerLazyLoading().subList(fromIndex, toIndex); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingMapProxy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingMapProxy.java new file mode 100644 index 000000000..d54abe8f2 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingMapProxy.java @@ -0,0 +1,129 @@ +package cz.cvut.kbss.jopa.proxy.lazy; + +import cz.cvut.kbss.jopa.exception.LazyLoadingException; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.utils.CollectionFactory; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * {@link Map} proxy that triggers lazy loading when its contents is accessed. + * + * @param Type of the object whose attribute value is this map proxy + * @param Map key type + * @param Map value type + */ +public class LazyLoadingMapProxy implements LazyLoadingProxy>, Map { + + protected final transient T owner; + protected final transient FieldSpecification> fieldSpec; + protected final transient UnitOfWork persistenceContext; + + private Map value; + + public LazyLoadingMapProxy(T owner, FieldSpecification> fieldSpec, + UnitOfWork persistenceContext) { + this.owner = owner; + this.fieldSpec = fieldSpec; + this.persistenceContext = persistenceContext; + } + + @Override + public Map triggerLazyLoading() { + if (value != null) { + return value; + } + if (persistenceContext == null || !persistenceContext.isActive()) { + throw new LazyLoadingException("No active persistence context is available in lazy loading proxy for attribute " + + fieldSpec + " of entity " + owner); + } + this.value = (Map) persistenceContext.loadEntityField(owner, fieldSpec); + return value; + } + + @Override + public boolean isLoaded() { + return value != null; + } + + @Override + public Map getLoadedValue() { + if (value == null) { + throw new IllegalStateException("Proxy has not been loaded, yet."); + } + return value; + } + + @Override + public Map unwrap() { + return (Map) CollectionFactory.createDefaultMap(); + } + + @Override + public int size() { + return triggerLazyLoading().size(); + } + + @Override + public boolean isEmpty() { + return triggerLazyLoading().isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return triggerLazyLoading().containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return triggerLazyLoading().containsValue(value); + } + + @Override + public V get(Object key) { + return triggerLazyLoading().get(key); + } + + @Override + public V put(K key, V value) { + return triggerLazyLoading().put(key, value); + } + + @Override + public V remove(Object key) { + return triggerLazyLoading().remove(key); + } + + @Override + public void putAll(Map m) { + triggerLazyLoading().putAll(m); + } + + @Override + public void clear() { + triggerLazyLoading().clear(); + } + + @Override + public Set keySet() { + return triggerLazyLoading().keySet(); + } + + @Override + public Collection values() { + return triggerLazyLoading().values(); + } + + @Override + public Set> entrySet() { + return triggerLazyLoading().entrySet(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + owner.getClass().getSimpleName() + "." + fieldSpec.getName() + "]"; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingProxy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingProxy.java new file mode 100644 index 000000000..0613fa13b --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingProxy.java @@ -0,0 +1,41 @@ +package cz.cvut.kbss.jopa.proxy.lazy; + +/** + * Marker interface for lazy loading proxy implementations. + * + * @param Type of the loaded value + */ +public interface LazyLoadingProxy { + + /** + * Triggers value loading. + * + * @return Loaded value + */ + T triggerLazyLoading(); + + /** + * Checks whether this proxy has already been loaded. + * + * @return {@code true} if this proxy has been loaded, {@code false} otherwise + */ + boolean isLoaded(); + + /** + * If this proxy has already been loaded, this method returns the loaded value. + * + * @return The loaded value, {@code null} if this proxy has not been loaded, yet + * @see #isLoaded() + * @throws IllegalStateException If called before this proxy is loaded + */ + T getLoadedValue(); + + /** + * Gets a value that should be used to replace this proxy outside a persistence context. + * + * @return Default value to replace this proxy with + */ + default T unwrap() { + return null; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingProxyFactory.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingProxyFactory.java new file mode 100644 index 000000000..25ae1a689 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingProxyFactory.java @@ -0,0 +1,57 @@ +package cz.cvut.kbss.jopa.proxy.lazy; + +import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.model.metamodel.ListAttribute; +import cz.cvut.kbss.jopa.proxy.lazy.gen.LazyLoadingEntityProxy; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Creates lazy-loading proxies for entity attributes. + *

    + * Needs a {@link UnitOfWork} to associate the proxies with, so that lazy loading can be executed when triggered. + */ +public class LazyLoadingProxyFactory { + + private final UnitOfWork uow; + + public LazyLoadingProxyFactory(UnitOfWork uow) { + this.uow = uow; + } + + /** + * Creates a lazy loading proxy for the value of the specified attribute. + * + * @param entity Entity whose attribute will be proxied + * @param fieldSpec Attribute to proxy + * @param Entity type + * @return Lazy loading proxy associated with a persistence context + */ + public Object createProxy(T entity, FieldSpecification fieldSpec) { + final Class type = fieldSpec.getJavaType(); + if (List.class.isAssignableFrom(type)) { + return new LazyLoadingListProxy<>(entity, (ListAttribute) fieldSpec, uow); + } else if (Set.class.isAssignableFrom(type)) { + return new LazyLoadingSetProxy<>(entity, (FieldSpecification) fieldSpec, uow); + } else if (Map.class.isAssignableFrom(type)) { + return new LazyLoadingMapProxy<>(entity, (FieldSpecification) fieldSpec, uow); + } else if (uow.getMetamodel().isEntityType(type)) { + try { + final Class proxyType = uow.getMetamodel().getLazyLoadingProxy(type); + final LazyLoadingEntityProxy proxy = (LazyLoadingEntityProxy) proxyType.getDeclaredConstructor().newInstance(); + proxy.setOwner(entity); + proxy.setPersistenceContext(uow); + proxy.setFieldSpec(fieldSpec); + return proxy; + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new OWLPersistenceException("Unable to instantiate lazy loading proxy!", e); + } + } + throw new IllegalArgumentException("Unsupported type for lazy proxying."); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingSetProxy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingSetProxy.java new file mode 100644 index 000000000..197d1039b --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingSetProxy.java @@ -0,0 +1,20 @@ +package cz.cvut.kbss.jopa.proxy.lazy; + +import cz.cvut.kbss.jopa.model.metamodel.CollectionType; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.utils.CollectionFactory; + +import java.util.Set; + +public class LazyLoadingSetProxy extends LazyLoadingCollectionProxy, E> implements Set { + public LazyLoadingSetProxy(O owner, FieldSpecification> fieldSpec, + UnitOfWork persistenceContext) { + super(owner, fieldSpec, persistenceContext); + } + + @Override + public Set unwrap() { + return (Set) CollectionFactory.createDefaultCollection(CollectionType.SET); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/GeneratedLazyLoadingProxy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/GeneratedLazyLoadingProxy.java new file mode 100644 index 000000000..bcfacfb97 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/GeneratedLazyLoadingProxy.java @@ -0,0 +1,14 @@ +package cz.cvut.kbss.jopa.proxy.lazy.gen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker interface for generated lazy loading proxy classes. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface GeneratedLazyLoadingProxy { +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/GeneratedLazyLoadingProxyImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/GeneratedLazyLoadingProxyImpl.java new file mode 100644 index 000000000..42e5e26a3 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/GeneratedLazyLoadingProxyImpl.java @@ -0,0 +1,10 @@ +package cz.cvut.kbss.jopa.proxy.lazy.gen; + +import java.lang.annotation.Annotation; + +public class GeneratedLazyLoadingProxyImpl implements GeneratedLazyLoadingProxy { + @Override + public Class annotationType() { + return GeneratedLazyLoadingProxy.class; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxy.java new file mode 100644 index 000000000..4cd0e53bd --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxy.java @@ -0,0 +1,48 @@ +package cz.cvut.kbss.jopa.proxy.lazy.gen; + +import cz.cvut.kbss.jopa.exception.LazyLoadingException; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; + +/** + * Implemented by generated lazy loading entity proxy classes. + *

    + * It defines the default method for triggering lazy loading. + * + * @param Type of the lazily-loaded value + */ +public interface LazyLoadingEntityProxy extends LazyLoadingProxyPropertyAccessor, LazyLoadingProxy { + + @Override + default T triggerLazyLoading() { + if (isLoaded()) { + return getLoadedValue(); + } + if (getPersistenceContext() == null || !getPersistenceContext().isActive()) { + throw new LazyLoadingException("No active persistence context is available in lazy loading proxy for attribute " + + getFieldSpec() + " of entity " + getOwner()); + } + setValue((T) getPersistenceContext().loadEntityField(getOwner(), (FieldSpecification) getFieldSpec())); + return getLoadedValue(); + } + + /** + * Gets the entity class for which this is a lazy loading proxy. + * + * @return Proxied entity class + */ + default Class getProxiedClass() { + assert getClass().getSuperclass() != null; + return (Class) getClass().getSuperclass(); + } + + /** + * Common implementation of {@link Object#toString()}. + * + * @return String representation of this proxy + */ + default String stringify() { + return getClass().getSimpleName() + "[" + getOwner().getClass() + .getSimpleName() + "." + getFieldSpec().getName() + "]"; + } +} 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 new file mode 100644 index 000000000..d958e2a82 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGenerator.java @@ -0,0 +1,112 @@ +package cz.cvut.kbss.jopa.proxy.lazy.gen; + +import cz.cvut.kbss.jopa.exception.LazyLoadingException; +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; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.NamingStrategy; +import net.bytebuddy.description.modifier.FieldPersistence; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.FieldValue; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.This; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Objects; + +import static net.bytebuddy.matcher.ElementMatchers.isGetter; +import static net.bytebuddy.matcher.ElementMatchers.isSetter; +import static net.bytebuddy.matcher.ElementMatchers.isToString; +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class LazyLoadingEntityProxyGenerator implements PersistenceContextAwareClassGenerator { + + private static final Logger LOG = LoggerFactory.getLogger(LazyLoadingEntityProxyGenerator.class); + + private final ByteBuddy byteBuddy = new ByteBuddy().with(new NamingStrategy.AbstractBase() { + @Override + protected String name(TypeDescription typeDescription) { + return typeDescription.getSimpleName() + "_LazyLoadingProxy"; + } + }); + + @Override + public Class generate(Class entityClass) { + Objects.requireNonNull(entityClass); + LOG.trace("Generating lazy loading proxy for entity class {}.", entityClass); + DynamicType.Unloaded typeDef = byteBuddy.subclass(entityClass) + .annotateType(new GeneratedLazyLoadingProxyImpl()) + .defineField("persistenceContext", UnitOfWork.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT) + .defineField("owner", Object.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT) + .defineField("fieldSpec", FieldSpecification.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT) + // Have to use Object, because otherwise it won't generate a setter for us + .defineField("value", entityClass, Visibility.PRIVATE, FieldPersistence.TRANSIENT) + .implement(TypeDescription.Generic.Builder.parameterizedType(LazyLoadingProxyPropertyAccessor.class, entityClass).build()) + .intercept(FieldAccessor.ofBeanProperty()) + .implement(LazyLoadingEntityProxy.class) + .method(isSetter().and(new PersistentPropertySetterMatcher<>(entityClass))) + .intercept(MethodDelegation.to(SetterInterceptor.class)) + .method(isGetter().and(new PersistentPropertyGetterMatcher<>(entityClass))) + .intercept(MethodDelegation.to(GetterInterceptor.class)) + .method(isToString()) + .intercept(MethodDelegation.toMethodReturnOf("stringify")) + .method(named("isLoaded")) + .intercept(MethodDelegation.to(ProxyMethodsInterceptor.class)) + .method(named("getLoadedValue")) + .intercept(MethodDelegation.to(ProxyMethodsInterceptor.class)) + .make(); + LOG.debug("Generated dynamic type {} for entity class {}.", typeDef, entityClass); + return typeDef.load(getClass().getClassLoader()).getLoaded(); + } + + public static class GetterInterceptor { + + @RuntimeType + public static Object get(@This LazyLoadingEntityProxy proxy, @Origin Method getter) { + final Object loaded = proxy.triggerLazyLoading(); + try { + return getter.invoke(loaded); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new LazyLoadingException("Unable to invoke getter after lazily loading object.", e); + } + } + } + + public static class SetterInterceptor { + + public static void set(@This LazyLoadingEntityProxy proxy, @Origin Method setter, + @AllArguments Object[] args) { + final Object loaded = proxy.triggerLazyLoading(); + try { + setter.invoke(loaded, args); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new LazyLoadingException("Unable to invoke setter after lazily loading object.", e); + } + } + } + + public static class ProxyMethodsInterceptor { + + public static boolean isLoaded(@This LazyLoadingEntityProxy proxy, @FieldValue("value") T value) { + return value != null; + } + + public static T getLoadedValue(@This LazyLoadingEntityProxy proxy, @FieldValue("value") T value) { + if (value == null) { + throw new IllegalStateException("Proxy has not been loaded, yet."); + } + return value; + } + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingProxyPropertyAccessor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingProxyPropertyAccessor.java new file mode 100644 index 000000000..c19038507 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingProxyPropertyAccessor.java @@ -0,0 +1,26 @@ +package cz.cvut.kbss.jopa.proxy.lazy.gen; + +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; + +/** + * Provides access to persistence context-related attributes needed by lazy loading proxies. + *

    + * This interface should be implemented by the classes generated by {@link LazyLoadingEntityProxyGenerator}. + */ +public interface LazyLoadingProxyPropertyAccessor { + + UnitOfWork getPersistenceContext(); + + void setPersistenceContext(UnitOfWork uow); + + FieldSpecification getFieldSpec(); + + void setFieldSpec(FieldSpecification fieldSpec); + + Object getOwner(); + + void setOwner(Object owner); + + void setValue(T value); +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/PersistentPropertyGetterMatcher.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/PersistentPropertyGetterMatcher.java new file mode 100644 index 000000000..85d6ab388 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/PersistentPropertyGetterMatcher.java @@ -0,0 +1,53 @@ +package cz.cvut.kbss.jopa.proxy.lazy.gen; + +import cz.cvut.kbss.jopa.model.metamodel.AnnotatedAccessor; +import cz.cvut.kbss.jopa.model.metamodel.gen.PersistentPropertyMatcher; +import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; + +import java.beans.Introspector; +import java.util.Optional; + +/** + * Matches only persistent field getters. + *

    + * This matcher checks that there exists a persistent field corresponding to the method name that appears to be a + * getter. {@code get, is, has} prefixes are supported. + * + * @param MethodDescription + */ +class PersistentPropertyGetterMatcher extends PersistentPropertyMatcher { + + PersistentPropertyGetterMatcher(Class parentType) { + super(parentType); + } + + @Override + protected boolean doMatch(T target) { + final String name = target.getName(); + final Optional fieldName = resolveFieldName(name, target); + return fieldName.flatMap(this::tryFindingField) + // Field must not be transient, and it must not be an identifier (no need generating getter interceptor + // for identifier) + .map(f -> !EntityPropertiesUtils.isFieldTransient(f)) + .orElse(false); + } + + @Override + protected Optional resolveFieldName(String methodName, MethodDescription methodDesc) { + assert methodDesc.getParameters().isEmpty(); + final TypeDescription.Generic returnType = methodDesc.getReturnType(); + if (methodName.startsWith(AnnotatedAccessor.GET_PREFIX)) { + return Optional.of(Introspector.decapitalize(methodName.substring(AnnotatedAccessor.GET_PREFIX.length()))); + } else if (methodName.startsWith(AnnotatedAccessor.IS_PREFIX) && (returnType.represents(Boolean.class))) { + return Optional.of(Introspector.decapitalize(methodName.substring(AnnotatedAccessor.IS_PREFIX.length()))); + } else if (methodName.startsWith(AnnotatedAccessor.HAS_PREFIX) && methodDesc.getReturnType() + .represents(Boolean.class)) { + return Optional.of(Introspector.decapitalize(methodName.substring(AnnotatedAccessor.HAS_PREFIX.length()))); + } else { + return Optional.empty(); + } + } +} + diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/AbstractPredicate.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/AbstractPredicate.java index 12948373f..9ceca1281 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/AbstractPredicate.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/AbstractPredicate.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.jopa.model.query.criteria.Expression; import cz.cvut.kbss.jopa.model.query.criteria.Predicate; import cz.cvut.kbss.jopa.query.criteria.expressions.AbstractExpression; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import java.util.List; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CompoundedPredicateImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CompoundedPredicateImpl.java index e1dbf5f55..dd1e1a96f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CompoundedPredicateImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CompoundedPredicateImpl.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.jopa.model.query.criteria.Expression; import cz.cvut.kbss.jopa.model.query.criteria.Predicate; import cz.cvut.kbss.jopa.query.criteria.expressions.AbstractExpression; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import java.util.List; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderImpl.java index 1dd7a38e6..82ba3f71c 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderImpl.java @@ -18,6 +18,8 @@ package cz.cvut.kbss.jopa.query.criteria; import cz.cvut.kbss.jopa.model.CriteriaQueryImpl; +import cz.cvut.kbss.jopa.model.metamodel.Metamodel; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import cz.cvut.kbss.jopa.model.query.criteria.Expression; import cz.cvut.kbss.jopa.model.query.criteria.Order; import cz.cvut.kbss.jopa.model.query.criteria.ParameterExpression; @@ -46,23 +48,21 @@ import cz.cvut.kbss.jopa.query.criteria.expressions.OrderImpl; import cz.cvut.kbss.jopa.query.criteria.expressions.ParameterExpressionImpl; import cz.cvut.kbss.jopa.query.criteria.expressions.UpperFunction; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; import java.util.Arrays; import java.util.Collection; public class CriteriaBuilderImpl implements CriteriaBuilder { - private final UnitOfWorkImpl uow; + private final Metamodel metamodel; - public CriteriaBuilderImpl(UnitOfWorkImpl uow) { - this.uow = uow; + public CriteriaBuilderImpl(Metamodel metamodel) { + this.metamodel = metamodel; } @Override public CriteriaQueryImpl createQuery(Class resultClass) { - return new CriteriaQueryImpl<>(new CriteriaQueryHolder<>(resultClass), uow.getMetamodel(), this); + return new CriteriaQueryImpl<>(new CriteriaQueryHolder<>(resultClass), metamodel, this); } @Override diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/PathImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/PathImpl.java index 0784b1aa9..278401f1b 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/PathImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/PathImpl.java @@ -23,7 +23,7 @@ import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; import cz.cvut.kbss.jopa.model.query.criteria.Path; import cz.cvut.kbss.jopa.query.criteria.expressions.AbstractPathExpression; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class PathImpl extends AbstractPathExpression implements Path { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/RootImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/RootImpl.java index b88cb544f..17bcfa04a 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/RootImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/RootImpl.java @@ -21,7 +21,7 @@ import cz.cvut.kbss.jopa.model.metamodel.Metamodel; import cz.cvut.kbss.jopa.model.query.criteria.Root; import cz.cvut.kbss.jopa.query.criteria.expressions.AbstractPathExpression; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class RootImpl extends AbstractPathExpression implements Root { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/SimplePredicateImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/SimplePredicateImpl.java index 9d27b8a1d..337b3e6c8 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/SimplePredicateImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/SimplePredicateImpl.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.jopa.model.query.criteria.Expression; import cz.cvut.kbss.jopa.model.query.criteria.Predicate; import cz.cvut.kbss.jopa.query.criteria.expressions.AbstractExpression; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import java.util.Collections; import java.util.List; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbsFunction.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbsFunction.java index 03f50e123..2eb02179f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbsFunction.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbsFunction.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.soql.SoqlConstants; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class AbsFunction extends AbstractFunctionExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractComparisonExpression.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractComparisonExpression.java index e46d7b0fd..d47887238 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractComparisonExpression.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractComparisonExpression.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public abstract class AbstractComparisonExpression extends AbstractExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractExpression.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractExpression.java index 52772b8f7..73d2cdf02 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractExpression.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractExpression.java @@ -21,8 +21,8 @@ import cz.cvut.kbss.jopa.model.query.criteria.Predicate; import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; import cz.cvut.kbss.jopa.query.criteria.SelectionImpl; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; -import cz.cvut.kbss.jopa.sessions.PredicateFactory; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.PredicateFactory; import java.util.Arrays; import java.util.Collection; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractFunctionExpression.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractFunctionExpression.java index dfcc9ba8a..804f568a0 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractFunctionExpression.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractFunctionExpression.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public abstract class AbstractFunctionExpression extends AbstractExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractPathExpression.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractPathExpression.java index a5c786118..6d9d6a825 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractPathExpression.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractPathExpression.java @@ -23,7 +23,7 @@ import cz.cvut.kbss.jopa.model.metamodel.SingularAttribute; import cz.cvut.kbss.jopa.model.query.criteria.Path; import cz.cvut.kbss.jopa.query.criteria.PathImpl; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public abstract class AbstractPathExpression extends AbstractExpression implements Path { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CeilFunction.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CeilFunction.java index 6bcc007fc..d4985738e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CeilFunction.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CeilFunction.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.soql.SoqlConstants; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class CeilFunction extends AbstractFunctionExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CountFunction.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CountFunction.java index a444adf99..8de3cf5f4 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CountFunction.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CountFunction.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.soql.SoqlConstants; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class CountFunction extends AbstractFunctionExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionEqualImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionEqualImpl.java index 3f94d260f..8eb743dd2 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionEqualImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionEqualImpl.java @@ -17,7 +17,7 @@ */ package cz.cvut.kbss.jopa.query.criteria.expressions; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class ExpressionEqualImpl extends AbstractComparisonExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionGreaterThanImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionGreaterThanImpl.java index 10a94d618..5ab7e9bfe 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionGreaterThanImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionGreaterThanImpl.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class ExpressionGreaterThanImpl extends AbstractComparisonExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionGreaterThanOrEqualImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionGreaterThanOrEqualImpl.java index 7441f0217..47cd2c8f2 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionGreaterThanOrEqualImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionGreaterThanOrEqualImpl.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class ExpressionGreaterThanOrEqualImpl extends AbstractComparisonExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionInImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionInImpl.java index 1f1ddcc76..3bd072b88 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionInImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionInImpl.java @@ -21,8 +21,8 @@ import cz.cvut.kbss.jopa.model.query.criteria.Predicate; import cz.cvut.kbss.jopa.query.criteria.AbstractPredicate; import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; -import cz.cvut.kbss.jopa.sessions.PredicateFactory; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.PredicateFactory; import java.util.ArrayList; import java.util.List; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLessThanImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLessThanImpl.java index c2b865574..40eadcc6c 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLessThanImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLessThanImpl.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class ExpressionLessThanImpl extends AbstractComparisonExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLessThanOrEqualImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLessThanOrEqualImpl.java index bed783676..bc5f63355 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLessThanOrEqualImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLessThanOrEqualImpl.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class ExpressionLessThanOrEqualImpl extends AbstractComparisonExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLikeImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLikeImpl.java index b0020a630..6706f1751 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLikeImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLikeImpl.java @@ -17,7 +17,7 @@ */ package cz.cvut.kbss.jopa.query.criteria.expressions; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class ExpressionLikeImpl extends AbstractComparisonExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLiteralImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLiteralImpl.java index 544613b12..b2a3027aa 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLiteralImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionLiteralImpl.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class ExpressionLiteralImpl extends AbstractExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionNotEqualImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionNotEqualImpl.java index 8b6fcf0ea..f8ca0d1ea 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionNotEqualImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionNotEqualImpl.java @@ -17,7 +17,7 @@ */ package cz.cvut.kbss.jopa.query.criteria.expressions; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class ExpressionNotEqualImpl extends AbstractComparisonExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionNotLikeImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionNotLikeImpl.java index 33e7747f7..57f48ad38 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionNotLikeImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ExpressionNotLikeImpl.java @@ -17,7 +17,7 @@ */ package cz.cvut.kbss.jopa.query.criteria.expressions; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class ExpressionNotLikeImpl extends AbstractComparisonExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/FloorFunction.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/FloorFunction.java index 7b30bba99..6a7b0af4a 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/FloorFunction.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/FloorFunction.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.soql.SoqlConstants; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class FloorFunction extends AbstractFunctionExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/IsMemberExpression.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/IsMemberExpression.java index 9f30b2e25..66537e642 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/IsMemberExpression.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/IsMemberExpression.java @@ -22,7 +22,7 @@ import cz.cvut.kbss.jopa.query.criteria.AbstractPredicate; import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; import cz.cvut.kbss.jopa.query.soql.SoqlConstants; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import java.util.Collection; import java.util.List; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LangFunction.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LangFunction.java index 34cd0a906..f9dc4cff7 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LangFunction.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LangFunction.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.soql.SoqlConstants; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; /** * Represents the SOQL {@link SoqlConstants.Functions#LANG} function. diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LengthFunction.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LengthFunction.java index fb0b0e140..17f287126 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LengthFunction.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LengthFunction.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.soql.SoqlConstants; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class LengthFunction extends AbstractFunctionExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LowerFunction.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LowerFunction.java index f3c8bd5d9..5b7895d82 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LowerFunction.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/LowerFunction.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.soql.SoqlConstants; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class LowerFunction extends AbstractFunctionExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ParameterExpressionImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ParameterExpressionImpl.java index efa5c1fc8..f5b5953ac 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ParameterExpressionImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ParameterExpressionImpl.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.jopa.model.query.Parameter; import cz.cvut.kbss.jopa.model.query.criteria.ParameterExpression; import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import java.util.Objects; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/UpperFunction.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/UpperFunction.java index 5a3bd37b6..59ca3fb0f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/UpperFunction.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/UpperFunction.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.criteria.expressions; import cz.cvut.kbss.jopa.query.soql.SoqlConstants; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; public class UpperFunction extends AbstractFunctionExpression { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ConstructorResultMapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ConstructorResultMapper.java index 198d13552..d05096d58 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ConstructorResultMapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ConstructorResultMapper.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.query.mapper; import cz.cvut.kbss.jopa.exception.SparqlResultMappingException; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.ontodriver.iteration.ResultRow; import cz.cvut.kbss.ontodriver.model.LangString; @@ -56,7 +56,7 @@ void addParameterMapper(VariableResultMapper mapper) { } @Override - public Object map(ResultRow resultRow, UnitOfWorkImpl uow) { + public Object map(ResultRow resultRow, UnitOfWork uow) { final Object[] values = new Object[paramMappers.size()]; final Class[] types = new Class[paramMappers.size()]; for (int i = 0; i < paramMappers.size(); i++) { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/EntityResultMapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/EntityResultMapper.java index 1e5c71c00..4e102daca 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/EntityResultMapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/EntityResultMapper.java @@ -18,10 +18,16 @@ package cz.cvut.kbss.jopa.query.mapper; import cz.cvut.kbss.jopa.exception.SparqlResultMappingException; +import cz.cvut.kbss.jopa.model.LoadState; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.lifecycle.PostLoadInvoker; import cz.cvut.kbss.jopa.model.metamodel.EntityType; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; +import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.jopa.utils.ReflectionUtils; import cz.cvut.kbss.ontodriver.iteration.ResultRow; @@ -34,11 +40,11 @@ */ class EntityResultMapper implements SparqlResultMapper { - private final EntityType et; + private final IdentifiableEntityType et; private final List fieldMappers = new ArrayList<>(); - EntityResultMapper(EntityType et) { + EntityResultMapper(IdentifiableEntityType et) { this.et = et; } @@ -55,12 +61,17 @@ EntityType getEntityType() { } @Override - public T map(ResultRow resultRow, UnitOfWorkImpl uow) { + public T map(ResultRow resultRow, UnitOfWork uow) { try { final T instance = ReflectionUtils.instantiateUsingDefaultConstructor(et.getJavaType()); - fieldMappers.forEach(m -> m.map(resultRow, instance, uow)); - return (T) uow.registerExistingObject(instance, new EntityDescriptor(), - Collections.singletonList(new PostLoadInvoker(uow.getMetamodel()))); + final LoadStateDescriptor loadStateDescriptor = LoadStateDescriptorFactory.createAllUnknown(instance, et); + fieldMappers.forEach(m -> { + m.map(resultRow, instance, uow); + loadStateDescriptor.setLoaded((FieldSpecification) m.getFieldSpecification(), LoadState.LOADED); + }); + uow.getLoadStateRegistry().put(instance, loadStateDescriptor); + return et.getJavaType() + .cast(uow.registerExistingObject(instance, new CloneRegistrationDescriptor(new EntityDescriptor()).postCloneHandlers(List.of(new PostLoadInvoker(uow.getMetamodel()))))); } catch (cz.cvut.kbss.jopa.exception.InstantiationException e) { // This is not expected, since an entity class must have a public no-arg constructor throw new SparqlResultMappingException(e); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ObjectPropertyFieldResultMapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ObjectPropertyFieldResultMapper.java index f89e1e502..1256823e7 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ObjectPropertyFieldResultMapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ObjectPropertyFieldResultMapper.java @@ -55,6 +55,6 @@ private Object resolveValue(UnitOfWork uow, Object id) { if (IdentifierTransformer.isValidIdentifierType(getFieldSpecification().getJavaType())) { return IdentifierTransformer.transformToIdentifier(id, getFieldSpecification().getJavaType()); } - return uow.readObject(getFieldSpecification().getJavaType(), id, new EntityDescriptor()); + return uow.readObjectWithoutRegistration(getFieldSpecification().getJavaType(), id, new EntityDescriptor()); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ResultRowMapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ResultRowMapper.java index 461ed254d..a8aabcba4 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ResultRowMapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ResultRowMapper.java @@ -17,7 +17,7 @@ */ package cz.cvut.kbss.jopa.query.mapper; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.ontodriver.iteration.ResultRow; import java.util.ArrayList; @@ -58,7 +58,7 @@ void addMapper(SparqlResultMapper mapper) { } @Override - public Object map(ResultRow resultRow, UnitOfWorkImpl uow) { + public Object map(ResultRow resultRow, UnitOfWork uow) { if (rowMappers.size() == 1) { return rowMappers.get(0).map(resultRow, uow); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ResultSetMappingProcessor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ResultSetMappingProcessor.java index 798a62295..f9ecbeab4 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ResultSetMappingProcessor.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/ResultSetMappingProcessor.java @@ -74,7 +74,7 @@ private static void buildConstructorMappers(SparqlResultSetMapping mapping, Resu private void buildEntityMappers(SparqlResultSetMapping mapping, ResultRowMapper parent) { for (EntityResult er : mapping.entities()) { - final EntityType et = getTargetType(er); + final IdentifiableEntityType et = getTargetType(er); final EntityResultMapper etMapper = new EntityResultMapper<>(et); generateFieldMappersForFieldResults(er, et, etMapper); generateFieldMappersForUnconfiguredFields(et, er).forEach(etMapper::addFieldMapper); @@ -82,14 +82,14 @@ private void buildEntityMappers(SparqlResultSetMapping mapping, ResultRowMapper } } - private EntityType getTargetType(EntityResult er) { + private IdentifiableEntityType getTargetType(EntityResult er) { final AbstractIdentifiableType targetType = metamodelBuilder.entity(er.entityClass()); - if (!(targetType instanceof EntityType)) { + if (targetType == null || targetType.isAbstract()) { throw new SparqlResultMappingException( "Type " + er.entityClass() + - " is not a known entity type and cannot be used as @EntityResult target class."); + " is not a known instantiable entity type and cannot be used as @EntityResult target class."); } - return (EntityType) targetType; + return (IdentifiableEntityType) targetType; } private static void generateFieldMappersForFieldResults(EntityResult er, EntityType et, diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/SparqlResultMapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/SparqlResultMapper.java index c22c7b6c7..5309e7f56 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/SparqlResultMapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/SparqlResultMapper.java @@ -17,7 +17,7 @@ */ package cz.cvut.kbss.jopa.query.mapper; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.ontodriver.iteration.ResultRow; /** @@ -34,5 +34,5 @@ public interface SparqlResultMapper { * @param uow Current persistence context * @return Result of the mapping */ - Object map(ResultRow resultRow, UnitOfWorkImpl uow); + Object map(ResultRow resultRow, UnitOfWork uow); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/VariableResultMapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/VariableResultMapper.java index 0855cc9be..16d38aa2a 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/VariableResultMapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/mapper/VariableResultMapper.java @@ -22,7 +22,7 @@ import cz.cvut.kbss.jopa.model.annotations.VariableResult; import cz.cvut.kbss.jopa.model.metamodel.Converters; import cz.cvut.kbss.jopa.oom.converter.ConverterWrapper; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.iteration.ResultRow; @@ -56,7 +56,7 @@ Class getTargetType() { * @return The mapped value */ @Override - public Object map(ResultRow resultRow, UnitOfWorkImpl uow) { + public Object map(ResultRow resultRow, UnitOfWork uow) { try { if (!resultRow.isBound(name)) { return null; 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 a72b83a68..fdfe2a60a 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 @@ -25,21 +25,22 @@ import cz.cvut.kbss.jopa.query.parameter.ParameterValueFactory; import cz.cvut.kbss.jopa.query.soql.SoqlQueryParser; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; -import cz.cvut.kbss.jopa.sessions.QueryFactory; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; -import cz.cvut.kbss.jopa.utils.ErrorUtils; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import java.util.Objects; -public class SparqlQueryFactory implements QueryFactory { +/** + * Factory for creating SPARQL queries. + */ +public class SparqlQueryFactory { - private final UnitOfWorkImpl uow; + private final UnitOfWork uow; private final ConnectionWrapper connection; private final QueryParser queryParser; private final SoqlQueryParser soqlQueryParser; - public SparqlQueryFactory(UnitOfWorkImpl uow, ConnectionWrapper connection) { + public SparqlQueryFactory(UnitOfWork uow, ConnectionWrapper connection) { assert uow != null; assert connection != null; this.uow = uow; @@ -48,57 +49,102 @@ public SparqlQueryFactory(UnitOfWorkImpl uow, ConnectionWrapper connection) { this.soqlQueryParser = new SoqlQueryParser(queryParser, uow.getMetamodel()); } - @Override + /** + * Creates query object representing a native SPARQL query. + * + * @param sparql The query + * @return Query object + * @throws NullPointerException If {@code sparql} is {@code null} + */ public QueryImpl createNativeQuery(String sparql) { Objects.requireNonNull(sparql); return new QueryImpl(queryParser.parseQuery(sparql), connection); } - @Override + /** + * Creates typed query object representing a native SPARQL query. + * + * @param sparql The query + * @param resultClass Type of the results + * @return Query object + * @throws NullPointerException If {@code sparql} or {@code resultClass} is {@code null} + */ public TypedQueryImpl createNativeQuery(String sparql, Class resultClass) { - Objects.requireNonNull(sparql, ErrorUtils.getNPXMessageSupplier("sparql")); + Objects.requireNonNull(sparql); return createQueryImpl(sparql, resultClass, queryParser); } private TypedQueryImpl createQueryImpl(String query, Class resultClass, QueryParser parser) { - Objects.requireNonNull(resultClass, ErrorUtils.getNPXMessageSupplier("resultClass")); + Objects.requireNonNull(resultClass); - final TypedQueryImpl tq = new TypedQueryImpl<>(parser.parseQuery(query), resultClass, connection, uow); - tq.setUnitOfWork(uow); - return tq; + return new TypedQueryImpl<>(parser.parseQuery(query), resultClass, connection, uow); } - @Override + /** + * Creates a query object representing a native SPARQL query. + * + * @param sparql The query + * @param resultSetMapping Name of the result set mapping to apply + * @return Query object * @throws NullPointerException If {@code sparql} or {@code resultSetMapping} is {@code null} + */ public QueryImpl createNativeQuery(String sparql, String resultSetMapping) { - Objects.requireNonNull(sparql, ErrorUtils.getNPXMessageSupplier("sparql")); - Objects.requireNonNull(resultSetMapping, ErrorUtils.getNPXMessageSupplier("resultSetMapping")); + Objects.requireNonNull(sparql); + Objects.requireNonNull(resultSetMapping); final SparqlResultMapper mapper = uow.getResultSetMappingManager().getMapper(resultSetMapping); return new ResultSetMappingQuery(queryParser.parseQuery(sparql), connection, mapper, uow); } - @Override + /** + * Creates query object representing a native SPARQL query. + * + * @param query The query + * @return Query object + * @throws NullPointerException If {@code sparql} is {@code null} + */ public QueryImpl createQuery(String query) { Objects.requireNonNull(query); return new QueryImpl(soqlQueryParser.parseQuery(query), connection); } - @Override + /** + * Creates typed query object representing a native SPARQL query. + * + * @param query The query + * @param resultClass Type of the results param URI of the ontology context against which the query will be + * evaluated + * @return Query object + * @throws NullPointerException If {@code sparql} or {@code resultClass} is {@code null} + */ public TypedQueryImpl createQuery(String query, Class resultClass) { - Objects.requireNonNull(query, ErrorUtils.getNPXMessageSupplier("query")); + Objects.requireNonNull(query); return createQueryImpl(query, resultClass, soqlQueryParser); } - @Override + /** + * Creates a query object representing a native SPARQL query. + * + * @param name The name of the query defined in metadata + * @return Query object + * @throws IllegalArgumentException If a query has not been defined with the given name + */ public QueryImpl createNamedQuery(String name) { final String query = uow.getNamedQueryManager().getQuery(name); return createNativeQuery(query); } - @Override + /** + * Creates a typed query object representing a native SPARQL query. + * + * @param name The name of the query defined in metadata + * @param resultClass Type of the results param URI of the ontology context against which the query will be + * evaluated + * @return Query object + * @throws IllegalArgumentException If a query has not been defined with the given name + */ public TypedQueryImpl createNamedQuery(String name, Class resultClass) { final String query = uow.getNamedQueryManager().getQuery(name); return createNativeQuery(query, resultClass); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryHolder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryHolder.java index 898acf30d..9345d71b6 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryHolder.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryHolder.java @@ -195,7 +195,7 @@ private static Optional assembleValuesClause(Set> para final StringBuilder variables = new StringBuilder(); final StringBuilder data = new StringBuilder(); for (QueryParameter qp : parameters) { - if (variables.length() > 0) { + if (!variables.isEmpty()) { variables.append(' '); } variables.append(qp.getIdentifierAsQueryString()); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractInstanceBuilder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractInstanceBuilder.java index f3481dfd4..d26d018b6 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractInstanceBuilder.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractInstanceBuilder.java @@ -17,21 +17,21 @@ */ package cz.cvut.kbss.jopa.sessions; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.security.PrivilegedActionException; abstract class AbstractInstanceBuilder { private static final Logger LOG = LoggerFactory.getLogger(AbstractInstanceBuilder.class); - protected final CloneBuilderImpl builder; - protected final UnitOfWorkImpl uow; + protected final CloneBuilder builder; + protected final UnitOfWork uow; - AbstractInstanceBuilder(CloneBuilderImpl builder, UnitOfWorkImpl uow) { + AbstractInstanceBuilder(CloneBuilder builder, UnitOfWork uow) { this.builder = builder; this.uow = uow; } @@ -90,18 +90,13 @@ protected static Constructor getDeclaredConstructorFor(final Class javaCla return null; } catch (RuntimeException e) { // Constructor cannot be resolved for some other reason - LOG.warn("Unable to get constructor for arguments of type {}. Got runtime exception {}.", args, e); + LOG.warn("Unable to get constructor for arguments of type {}.", args, e); return null; } return c; } protected static void logConstructorAccessException(Constructor constructor, Exception e) { - LOG.warn("Exception caught when invoking constructor " + constructor + ". Exception: " + e); - } - - protected static void logPrivilegedConstructorAccessException(Constructor constructor, - PrivilegedActionException e) { - LOG.warn("Exception caught on privileged invocation of constructor " + constructor + ". Exception: " + e); + LOG.warn("Exception caught when invoking constructor {}.", constructor, e); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractSession.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractSession.java index becc723a9..61c3f1a43 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractSession.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractSession.java @@ -17,45 +17,21 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.model.MetamodelImpl; -import cz.cvut.kbss.jopa.query.NamedQueryManager; -import cz.cvut.kbss.jopa.query.ResultSetMappingManager; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import cz.cvut.kbss.jopa.utils.Configuration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import java.util.Objects; /** - * This is the implementation of the basic Session operations. Other more - * specific methods are to be implemented in descendants. + * Defines common session-related methods. */ -public abstract class AbstractSession implements Session, MetamodelProvider, ConfigurationHolder { - - protected static final Logger LOG = LoggerFactory.getLogger(AbstractSession.class); +abstract class AbstractSession implements MetamodelProvider, ConfigurationHolder { protected Configuration configuration; protected AbstractSession(Configuration configuration) { - this.configuration = configuration; - } - - @Override - public UnitOfWork acquireUnitOfWork() { - // TODO UoW acquisition needs to be provided with configuration from EntityManager, because it may override the server session config - UnitOfWork uow = new UnitOfWorkImpl(this); - LOG.trace("UnitOfWork acquired."); - return uow; - } - - @Override - public abstract MetamodelImpl getMetamodel(); - - /** - * This method just releases the live object cache. Subclasses are free to - * make additional cleanup. - */ - @Override - public void release() { - getLiveObjectCache().evictAll(); + this.configuration = Objects.requireNonNull(configuration); } @Override @@ -68,7 +44,7 @@ public Configuration getConfiguration() { *

    * This manager represents the second level cache. * - * @return Second level cache + * @return Second level cache manager */ public abstract CacheManager getLiveObjectCache(); @@ -80,15 +56,9 @@ public Configuration getConfiguration() { protected abstract ConnectionWrapper acquireConnection(); /** - * Gets an object managing named queries in this persistence unit. + * Gets a {@link CriteriaBuilder} instance for building Criteria API queries. * - * @return {@link NamedQueryManager} - */ - public abstract NamedQueryManager getNamedQueryManager(); - - /** - * Gets the manager of SPARQL result set mapping instances. - * @return {@link ResultSetMappingManager} + * @return Criteria query builder */ - public abstract ResultSetMappingManager getResultSetMappingManager(); + public abstract CriteriaBuilder getCriteriaBuilder(); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java similarity index 51% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkImpl.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java index c8c35774c..868c185fe 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java @@ -17,40 +17,47 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectWrapper; 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.model.AbstractEntityManager; -import cz.cvut.kbss.jopa.model.BeanListenerAspect; -import cz.cvut.kbss.jopa.model.EntityManagerImpl.State; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; +import cz.cvut.kbss.jopa.model.EntityState; 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.annotations.FetchType; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.lifecycle.PostLoadInvoker; 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.query.NamedQueryManager; -import cz.cvut.kbss.jopa.query.ResultSetMappingManager; -import cz.cvut.kbss.jopa.query.criteria.CriteriaBuilderImpl; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory; -import cz.cvut.kbss.jopa.sessions.change.ChangeManagerImpl; -import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl; +import cz.cvut.kbss.jopa.sessions.cache.Descriptors; +import cz.cvut.kbss.jopa.sessions.change.Change; +import cz.cvut.kbss.jopa.sessions.change.ChangeCalculator; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory; -import cz.cvut.kbss.jopa.sessions.descriptor.InstanceDescriptor; -import cz.cvut.kbss.jopa.sessions.descriptor.InstanceDescriptorFactory; -import cz.cvut.kbss.jopa.sessions.validator.AttributeModificationValidator; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; +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.CloneConfiguration; +import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.jopa.sessions.validator.InferredAttributeChangeValidator; import cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator; +import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; -import cz.cvut.kbss.jopa.utils.Wrapper; -import org.aspectj.lang.Aspects; +import cz.cvut.kbss.jopa.utils.IdentifierTransformer; +import cz.cvut.kbss.jopa.utils.MetamodelUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.net.URI; -import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; @@ -58,87 +65,189 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; import static cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException.individualAlreadyManaged; import static cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator.getValidator; import static cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator.isNotInferred; import static cz.cvut.kbss.jopa.utils.EntityPropertiesUtils.getValueAsURI; -public class UnitOfWorkImpl extends AbstractSession implements UnitOfWork, ConfigurationHolder, Wrapper { +public abstract class AbstractUnitOfWork extends AbstractSession implements UnitOfWork { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractUnitOfWork.class); + final IndirectWrapperHelper indirectWrapperHelper; // Read-only!!! It is just the keyset of cloneToOriginals - private final Set cloneMapping; - private final Map cloneToOriginals; - private final Map keysToClones = new HashMap<>(); - private final Map deletedObjects; - private final Map newObjectsCloneToOriginal; - private final Map newObjectsKeyToClone = new HashMap<>(); - private final Map instanceDescriptors; - private RepositoryMap repoMap; - - private boolean hasChanges; - private boolean hasNew; - private boolean hasDeleted; - private boolean shouldReleaseAfterCommit; - private boolean shouldClearCacheAfterCommit; + final Set cloneMapping; + final Map cloneToOriginals; + final Map keysToClones = new HashMap<>(); + final Map deletedObjects; + final Map newObjectsCloneToOriginal; + final Map newObjectsKeyToClone = new HashMap<>(); + RepositoryMap repoMap; + + final LoadStateDescriptorRegistry loadStateRegistry; + boolean hasChanges; + boolean hasNew; + boolean hasDeleted; + + private boolean transactionActive; private boolean isActive; private boolean inCommit; - private UnitOfWorkChangeSet uowChangeSet = ChangeSetFactory.createUoWChangeSet(); + UnitOfWorkChangeSet uowChangeSet = ChangeSetFactory.createUoWChangeSet(); - private final AbstractSession parent; - private AbstractEntityManager entityManager; - private final ConnectionWrapper storage; + final AbstractSession parent; + final ConnectionWrapper storage; - private final MergeManager mergeManager; - private final CloneBuilder cloneBuilder; - private final ChangeManager changeManager; - private final SparqlQueryFactory queryFactory; - private final CriteriaBuilder criteriaFactory; - private final IndirectWrapperHelper indirectWrapperHelper; - private final InferredAttributeChangeValidator inferredAttributeChangeValidator; - /** - * This is a shortcut for the second level cache. - */ - private final CacheManager cacheManager; + final MergeManager mergeManager; + final CloneBuilder cloneBuilder; + final ChangeCalculator changeCalculator; + final SparqlQueryFactory queryFactory; + final InferredAttributeChangeValidator inferredAttributeChangeValidator; - public UnitOfWorkImpl(AbstractSession parent) { - super(parent.getConfiguration()); + public AbstractUnitOfWork(AbstractSession parent, Configuration configuration) { + super(configuration); this.parent = Objects.requireNonNull(parent); - this.cloneToOriginals = createMap(); + this.cloneToOriginals = new IdentityHashMap<>(); this.cloneMapping = cloneToOriginals.keySet(); - this.deletedObjects = createMap(); - this.newObjectsCloneToOriginal = createMap(); - this.instanceDescriptors = new IdentityHashMap<>(); + this.deletedObjects = new IdentityHashMap<>(); + this.newObjectsCloneToOriginal = new IdentityHashMap<>(); this.repoMap = new RepositoryMap(); - this.cloneBuilder = new CloneBuilderImpl(this); + this.loadStateRegistry = new LoadStateDescriptorRegistry(this::stringify); this.indirectWrapperHelper = new IndirectWrapperHelper(this); - this.cacheManager = parent.getLiveObjectCache(); + this.cloneBuilder = new CloneBuilder(this); this.storage = acquireConnection(); this.queryFactory = new SparqlQueryFactory(this, storage); - this.criteriaFactory = new CriteriaBuilderImpl(this); - this.mergeManager = new MergeManagerImpl(this); - this.changeManager = new ChangeManagerImpl(this); + this.mergeManager = new MergeManager(this, cloneBuilder); + this.changeCalculator = new ChangeCalculator(this); this.inferredAttributeChangeValidator = new InferredAttributeChangeValidator(storage); this.isActive = true; } - CloneBuilder getCloneBuilder() { - return cloneBuilder; + @Override + protected ConnectionWrapper acquireConnection() { + final ConnectionWrapper conn = parent.acquireConnection(); + conn.setUnitOfWork(this); + return conn; } @Override - public UnitOfWork acquireUnitOfWork() { - throw new UnsupportedOperationException("Nested UoWs are not supported."); + public void release() { + clear(); + storage.close(); + this.isActive = false; + LOG.debug("UnitOfWork released."); } @Override - protected ConnectionWrapper acquireConnection() { - final ConnectionWrapper conn = parent.acquireConnection(); - conn.setUnitOfWork(this); - return conn; + public void clear() { + detachAllManagedInstances(); + cloneToOriginals.clear(); + keysToClones.clear(); + deletedObjects.clear(); + newObjectsCloneToOriginal.clear(); + newObjectsKeyToClone.clear(); + loadStateRegistry.clear(); + this.hasChanges = false; + this.hasDeleted = false; + this.hasNew = false; + cloneBuilder.reset(); + this.repoMap = new RepositoryMap(); + repoMap.initDescriptors(); + this.uowChangeSet = ChangeSetFactory.createUoWChangeSet(); + this.transactionActive = false; + } + + /** + * Detaches all managed entities from this persistence context. + */ + abstract void detachAllManagedInstances(); + + @Override + public CacheManager getLiveObjectCache() { + return parent.getLiveObjectCache(); + } + + @Override + public boolean isActive() { + return isActive; + } + + @Override + public void begin() { + this.transactionActive = true; + } + + @Override + public void commit() { + LOG.trace("UnitOfWork commit started."); + if (!isActive()) { + throw new IllegalStateException("Cannot commit inactive Unit of Work!"); + } + this.inCommit = true; + commitUnitOfWork(); + LOG.trace("UnitOfWork commit finished."); + } + + /** + * Commit this Unit of Work. + */ + private void commitUnitOfWork() { + commitToStorage(); + mergeChangesIntoParent(); + postCommit(); + } + + /** + * If there are any changes, commit them to the ontology. + */ + abstract void commitToStorage(); + + /** + * Merge the changes from this Unit of Work's change set into the server session. + */ + private void mergeChangesIntoParent() { + if (hasChanges()) { + mergeManager.mergeChangesFromChangeSet(uowChangeSet); + } + evictPossiblyUpdatedReferencesFromCache(); + } + + private void evictPossiblyUpdatedReferencesFromCache() { + cloneToOriginals.forEach((clone, orig) -> { + if (orig == null && !deletedObjects.containsKey(clone)) { + removeObjectFromCache(clone, getDescriptor(clone).getSingleContext().orElse(null)); + } + }); + } + + /** + * Cleans up after the commit. + */ + private void postCommit() { + final boolean changes = hasChanges(); + clear(); + this.inCommit = false; + if (changes) { + getLiveObjectCache().evictInferredObjects(); + } + } + + @Override + public void rollback() { + LOG.trace("UnitOfWork rollback started."); + if (!isActive()) { + throw new IllegalStateException("Cannot rollback inactive Unit of Work!"); + } + storage.rollback(); + clear(); + } + + @Override + public boolean contains(Object entity) { + Objects.requireNonNull(entity); + return isObjectManaged(entity); } @Override @@ -150,7 +259,7 @@ public T readObject(Class cls, Object identifier, Descriptor descriptor) return readObjectInternal(cls, identifier, descriptor); } - private T readObjectInternal(Class cls, Object identifier, Descriptor descriptor) { + protected T readObjectInternal(Class cls, Object identifier, Descriptor descriptor) { assert cls != null; assert identifier != null; assert descriptor != null; @@ -163,13 +272,11 @@ private T readObjectInternal(Class cls, Object identifier, Descriptor des if (result == null) { return null; } - final Object clone = registerExistingObject(result, descriptor, - Collections.singletonList(new PostLoadInvoker(getMetamodel()))); - checkForIndirectObjects(clone); + final Object clone = registerExistingObject(result, new CloneRegistrationDescriptor(descriptor).postCloneHandlers(List.of(new PostLoadInvoker(getMetamodel())))); return cls.cast(clone); } - private T readManagedObject(Class cls, Object identifier, Descriptor descriptor) { + T readManagedObject(Class cls, Object identifier, Descriptor descriptor) { // First try to find the object among new uncommitted objects Object result = newObjectsKeyToClone.get(identifier); if (result != null && (isInRepository(descriptor, result))) { @@ -180,6 +287,13 @@ private T readManagedObject(Class cls, Object identifier, Descriptor desc return getManagedClone(cls, identifier, descriptor); } + private boolean isInRepository(Descriptor descriptor, Object entity) { + assert descriptor != null; + assert entity != null; + + return repoMap.contains(descriptor, entity); + } + private T getManagedClone(Class cls, Object identifier, Descriptor descriptor) { if (!keysToClones.containsKey(identifier)) { return null; @@ -193,222 +307,190 @@ private T getManagedClone(Class cls, Object identifier, Descriptor descri @Override public T getReference(Class cls, Object identifier, Descriptor descriptor) { + // TODO Temporary just so that the API works + return readObject(cls, identifier, descriptor); + } + + @Override + public T readObjectWithoutRegistration(Class cls, Object identifier, Descriptor descriptor) { Objects.requireNonNull(cls); Objects.requireNonNull(identifier); Objects.requireNonNull(descriptor); - final T managedResult = readManagedObject(cls, identifier, descriptor); - if (managedResult != null) { - return managedResult; - } - final T result = storage.getReference(new LoadingParameters<>(cls, getValueAsURI(identifier), descriptor)); - if (result == null) { - return null; - } - instanceDescriptors.put(result, InstanceDescriptorFactory.createNotLoaded(result, entityType(cls))); - registerEntityWithPersistenceContext(result); - registerEntityWithOntologyContext(result, descriptor); - if (getLiveObjectCache().contains(cls, identifier, descriptor)) { - cloneToOriginals.put(result, getLiveObjectCache().get(cls, identifier, descriptor)); - } else { - cloneToOriginals.put(result, null); + T result = readManagedObject(cls, identifier, descriptor); + if (result != null) { + return result; } - keysToClones.put(identifier, result); - return result; + return storage.find(new LoadingParameters<>(cls, getValueAsURI(identifier), descriptor)); } - /** - * This method calculates the changes that were to the registered entities and adds these changes into the given - * change set for future commit to the ontology. - */ - private void calculateChanges() { - if (hasNew) { - calculateNewObjects(uowChangeSet); - } - if (hasDeleted) { - calculateDeletedObjects(uowChangeSet); - } - } + @Override + public EntityState getState(Object entity) { + Objects.requireNonNull(entity); - /** - * Create object change sets for the new objects and adds them into our UnitOfWorkChangeSet. - * - * @param changeSet UnitOfWorkChangeSet - */ - private void calculateNewObjects(UnitOfWorkChangeSet changeSet) { - for (Object clone : newObjectsCloneToOriginal.keySet()) { - final Descriptor c = getDescriptor(clone); - Object original = newObjectsCloneToOriginal - .computeIfAbsent(clone, key -> cloneBuilder.buildClone(key, new CloneConfiguration(c))); - if (original == null) { - throw new OWLPersistenceException( - "Error while calculating changes for new objects. Original not found."); - } - newObjectsCloneToOriginal.put(clone, original); - changeSet.addNewObjectChangeSet(ChangeSetFactory.createObjectChangeSet(original, clone, c)); + if (deletedObjects.containsKey(entity)) { + return EntityState.REMOVED; + } else if (newObjectsCloneToOriginal.containsKey(entity)) { + return EntityState.MANAGED_NEW; + } else if (cloneMapping.contains(entity)) { + return EntityState.MANAGED; + } else { + return EntityState.NOT_MANAGED; } } - private void calculateDeletedObjects(final UnitOfWorkChangeSet changeSet) { - for (Object clone : deletedObjects.keySet()) { - Descriptor descriptor = getDescriptor(clone); - changeSet.addDeletedObjectChangeSet(ChangeSetFactory.createDeleteObjectChangeSet(clone, descriptor)); - changeSet.cancelObjectChanges(getOriginal(clone)); + @Override + public EntityState getState(Object entity, Descriptor descriptor) { + Objects.requireNonNull(entity); + Objects.requireNonNull(descriptor); + + if (deletedObjects.containsKey(entity)) { + return EntityState.REMOVED; + } else if (newObjectsCloneToOriginal.containsKey(entity) && isInRepository(descriptor, entity)) { + return EntityState.MANAGED_NEW; + } else if (cloneMapping.contains(entity) && isInRepository(descriptor, entity)) { + return EntityState.MANAGED; + } else { + return EntityState.NOT_MANAGED; } } @Override - public void clear() { - detachAllManagedInstances(); - cloneToOriginals.clear(); - keysToClones.clear(); - deletedObjects.clear(); - newObjectsCloneToOriginal.clear(); - newObjectsKeyToClone.clear(); - instanceDescriptors.clear(); - this.hasChanges = false; - this.hasDeleted = false; - this.hasNew = false; - cloneBuilder.reset(); - this.repoMap = new RepositoryMap(); - repoMap.initDescriptors(); - this.uowChangeSet = ChangeSetFactory.createUoWChangeSet(); - } - - private void detachAllManagedInstances() { - cloneMapping.forEach(instance -> { - removeIndirectWrappers(instance); - deregisterEntityFromPersistenceContext(instance); - }); - newObjectsCloneToOriginal.keySet().forEach(instance -> { - removeIndirectWrappers(instance); - deregisterEntityFromPersistenceContext(instance); - }); + public boolean isObjectNew(Object entity) { + return entity != null && newObjectsCloneToOriginal.containsKey(entity); } @Override - public boolean contains(Object entity) { + public boolean isObjectManaged(Object entity) { Objects.requireNonNull(entity); - return isObjectManaged(entity); + + return cloneMapping.contains(entity) && !deletedObjects.containsKey(entity) || newObjectsCloneToOriginal.containsKey(entity); } @Override - public void commit() { - LOG.trace("UnitOfWork commit started."); - if (!isActive()) { - throw new IllegalStateException("Cannot commit inactive Unit of Work!"); + public T mergeDetached(T entity, Descriptor descriptor) { + Objects.requireNonNull(entity); + Objects.requireNonNull(descriptor); + + final Object id = getIdentifier(entity); + if (!storage.contains(id, entity.getClass(), descriptor)) { + registerNewObject(entity, descriptor); + return entity; + } else { + if (isIndividualManaged(id, entity) && !isSameType(id, entity)) { + throw individualAlreadyManaged(id); + } + return mergeDetachedInternal(entity, descriptor); } - this.inCommit = true; - commitUnitOfWork(); - LOG.trace("UnitOfWork commit finished."); } - @Override - public void rollback() { - LOG.trace("UnitOfWork rollback started."); - if (!isActive()) { - throw new IllegalStateException("Cannot rollback inactive Unit of Work!"); - } - storage.rollback(); - clear(); + Object getIdentifier(Object entity) { + return EntityPropertiesUtils.getIdentifier(entity, getMetamodel()); } - /** - * Commit this Unit of Work. - */ - private void commitUnitOfWork() { - commitToOntology(); - mergeChangesIntoParent(); - postCommit(); + private boolean isSameType(Object id, Object entity) { + final Class mergedType = entity.getClass(); + final Object managed = keysToClones.containsKey(id) ? keysToClones.get(id) : newObjectsKeyToClone.get(id); + assert managed != null; + final Class managedType = MetamodelUtils.getEntityClass(managed.getClass()); + return managedType.isAssignableFrom(mergedType); } /** - * Clean up after the commit. + * Merges the specified detached entity into this persistence context. + * + * @param entity Entity to merge + * @param descriptor Descriptor of the merged entity + * @param Entity type + * @return Managed instance */ - private void postCommit() { - final boolean changes = hasChanges(); - clear(); - this.inCommit = false; - if (changes) { - if (shouldClearCacheAfterCommit) { - cacheManager.evictAll(); - this.shouldReleaseAfterCommit = true; - } else { - cacheManager.evictInferredObjects(); - } - } + abstract T mergeDetachedInternal(T entity, Descriptor descriptor); + @Override + public Object registerExistingObject(Object entity, Descriptor descriptor) { + return registerExistingObject(entity, new CloneRegistrationDescriptor(descriptor)); } - /** - * If there are any changes, commit them to the ontology. - */ - private void commitToOntology() { - if (this.hasNew || this.hasChanges || this.hasDeleted) { - calculateChanges(); + @Override + public Object registerExistingObject(Object entity, CloneRegistrationDescriptor registrationDescriptor) { + if (entity == null) { + return null; + } + if (cloneToOriginals.containsValue(entity)) { + return getCloneForOriginal(entity); } - validateIntegrityConstraints(); - storageCommit(); + final CloneConfiguration cloneConfig = CloneConfiguration.withDescriptor(registrationDescriptor.getDescriptor()) + .forPersistenceContext(!isInCommit()) + .addPostRegisterHandlers(registrationDescriptor.getPostCloneHandlers()); + Object clone = cloneBuilder.buildClone(entity, cloneConfig); + assert clone != null; + registerClone(clone, entity, registrationDescriptor.getDescriptor()); + registrationDescriptor.getPostCloneHandlers().forEach(c -> c.accept(clone)); + return clone; } - private void validateIntegrityConstraints() { - final IntegrityConstraintsValidator validator = getValidator(); - for (ObjectChangeSet changeSet : uowChangeSet.getNewObjects()) { - validator.validate(changeSet.getCloneObject(), entityType((Class) changeSet.getObjectClass()), - isNotInferred()); - } - uowChangeSet.getExistingObjectsChanges().forEach(changeSet -> validator.validate(changeSet, getMetamodel())); + void registerClone(Object clone, Object original, Descriptor descriptor) { + cloneToOriginals.put(clone, original); + final Object identifier = EntityPropertiesUtils.getIdentifier(clone, getMetamodel()); + keysToClones.put(identifier, clone); + registerEntityWithOntologyContext(clone, descriptor); } - private static Map createMap() { - return new IdentityHashMap<>(); + protected IdentifiableEntityType entityType(Class cls) { + return getMetamodel().entity(cls); } /** - * Gets current state of the specified entity. - *

    - * Note that since no repository is specified we can only determine if the entity is managed or removed. Therefore - * if the case is different this method returns State#NOT_MANAGED. - * - * @param entity The entity to check - * @return State of the entity + * This method calculates the changes that were to the registered entities and adds these changes into the given + * change set for future commit to the ontology. */ - public State getState(Object entity) { - Objects.requireNonNull(entity); - - if (deletedObjects.containsKey(entity)) { - return State.REMOVED; - } else if (newObjectsCloneToOriginal.containsKey(entity)) { - return State.MANAGED_NEW; - } else if (cloneMapping.contains(entity)) { - return State.MANAGED; - } else { - return State.NOT_MANAGED; + void calculateChanges() { + if (hasNew) { + calculateNewObjects(uowChangeSet); + } + if (hasDeleted) { + calculateDeletedObjects(uowChangeSet); } } /** - * Checks the state of the specified entity with regards to the specified repository. - * - * @param entity Object - * @param descriptor Entity descriptor - * @return The state of the specified entity + * Create object change sets for the new objects and adds them into our UnitOfWorkChangeSet. */ - public State getState(Object entity, Descriptor descriptor) { - Objects.requireNonNull(entity); - Objects.requireNonNull(descriptor); + private void calculateNewObjects(UnitOfWorkChangeSet changeSet) { + for (Object clone : newObjectsCloneToOriginal.keySet()) { + final Descriptor c = getDescriptor(clone); + changeSet.addNewObjectChangeSet(ChangeSetFactory.createNewObjectChange(clone, c)); + } + } - if (deletedObjects.containsKey(entity)) { - return State.REMOVED; - } else if (newObjectsCloneToOriginal.containsKey(entity) && isInRepository(descriptor, entity)) { - return State.MANAGED_NEW; - } else if (cloneMapping.contains(entity) && isInRepository(descriptor, entity)) { - return State.MANAGED; - } else { - return State.NOT_MANAGED; + private void calculateDeletedObjects(final UnitOfWorkChangeSet changeSet) { + for (Object clone : deletedObjects.keySet()) { + final Descriptor descriptor = getDescriptor(clone); + final Object original = getOriginal(clone); + changeSet.addDeletedObjectChangeSet(ChangeSetFactory.createDeleteObjectChange(clone, original, descriptor)); + changeSet.cancelObjectChanges(original); + } + } + + void persistNewObjects() { + if (hasNew) { + newObjectsKeyToClone.forEach((id, entity) -> { + final Descriptor descriptor = getDescriptor(entity); + storage.persist(id, entity, descriptor); + final IdentifiableEntityType et = entityType(entity.getClass()); + et.getLifecycleListenerManager().invokePostPersistCallbacks(entity); + }); } } + void validateIntegrityConstraints() { + final IntegrityConstraintsValidator validator = getValidator(); + for (Change changeSet : uowChangeSet.getNewObjects()) { + validator.validate(changeSet.getClone(), entityType((Class) changeSet.getObjectClass()), isNotInferred()); + } + uowChangeSet.getExistingObjectsChanges().forEach(changeSet -> validator.validate(changeSet, getMetamodel())); + } + /** * Tries to find the original object for the given clone. It searches the existing objects, new objects and deleted * objects. @@ -424,326 +506,124 @@ public Object getOriginal(Object clone) { } /** - * Gets managed original with the specified identifier or {@code null} if there is none matching. + * Registers the specified original for the specified clone, assuming the clone is a new object. *

    - * Descriptor is used to check repository context validity. - * - * @param cls Return type of the original - * @param identifier Instance identifier - * @param descriptor Repository descriptor - * @return Original object managed by this UoW or {@code null} if this UoW doesn't contain a matching instance - */ - public T getManagedOriginal(Class cls, Object identifier, Descriptor descriptor) { - final T clone = getManagedClone(cls, identifier, descriptor); - return clone != null ? cls.cast(cloneToOriginals.get(clone)) : null; - } - - /** - * Check if this UnitOfWork contains this original entity. This method is used by the CloneBuilder so it does not - * have to clone already managed referenced objects. - * - * @param entity The original entity. - * @return True if the original is managed in this UnitOfWork. - */ - boolean containsOriginal(Object entity) { - return entity != null && cloneToOriginals.containsValue(entity); - } - - /** - * Finds clone of the specified original. - * - * @param original The original object whose clone we are looking for - * @return The clone or null, if there is none - */ - public Object getCloneForOriginal(Object original) { - for (Entry entry : cloneToOriginals.entrySet()) { - // We use IdentityMap, so we can use == - if (entry.getValue() == original) { - return entry.getKey(); - } - } - return null; - } - - public boolean hasChanges() { - return hasChanges || hasDeleted || hasNew; - } - - void setHasChanges() { - this.hasChanges = true; - } - - @Override - public CacheManager getLiveObjectCache() { - return parent.getLiveObjectCache(); - } - - UnitOfWorkChangeSet getUowChangeSet() { - return uowChangeSet; - } - - @Override - public boolean isActive() { - return this.isActive; - } - - /** - * Returns true if the given clone represents a newly created object. Otherwise returns false. - * - * @param clone Object - * @return boolean - */ - public boolean isObjectNew(Object clone) { - return clone != null && newObjectsCloneToOriginal.containsKey(clone); - } - - /** - * Returns true if the given object is already managed. + * This method must be called during commit when new objects are persisted so that they can be referenced by other + * objects. * - * @param entity Object - * @return boolean + * @param clone Already registered clone + * @param original Original to register */ - @Override - public boolean isObjectManaged(Object entity) { - Objects.requireNonNull(entity); - - return cloneMapping.contains(entity) && !deletedObjects.containsKey(entity) || - newObjectsCloneToOriginal.containsKey(entity); + public void registerOriginalForNewClone(Object clone, Object original) { + assert inCommit; + assert newObjectsCloneToOriginal.containsKey(clone); + newObjectsCloneToOriginal.put(clone, original); } /** - * Persists changed value of the specified field. + * Gets managed original with the specified identifier or {@code null} if there is none matching. + *

    + * Descriptor is used to check repository context validity. * - * @param entity Entity with changes (the clone) - * @param f The field whose value has changed - * @throws IllegalStateException If this UoW is not in transaction - */ - public void attributeChanged(Object entity, Field f) { - if (!isInTransaction()) { - throw new IllegalStateException("This unit of work is not in a transaction."); - } - final Descriptor descriptor = getDescriptor(entity); - final IdentifiableEntityType et = entityType((Class) entity.getClass()); - final FieldSpecification fieldSpec = et.getFieldSpecification(f.getName()); - final Object original = getOriginal(entity); - if (fieldSpec.isInferred() && original != null) { - inferredAttributeChangeValidator.validateChange(entity, getOriginal(entity), fieldSpec, descriptor); - } - et.getLifecycleListenerManager().invokePreUpdateCallbacks(entity); - storage.merge(entity, fieldSpec, descriptor); - createAndRegisterChangeRecord(entity, fieldSpec, descriptor); - setHasChanges(); - setIndirectObjectIfPresent(entity, f); - et.getLifecycleListenerManager().invokePostUpdateCallbacks(entity); - instanceDescriptors.get(entity).setLoaded(fieldSpec, LoadState.LOADED); - } - - private void createAndRegisterChangeRecord(Object clone, FieldSpecification fieldSpec, - Descriptor descriptor) { - final Object orig = getOriginal(clone); - if (orig == null) { - return; - } - final ChangeRecord record = new ChangeRecordImpl(fieldSpec, - EntityPropertiesUtils.getFieldValue(fieldSpec.getJavaField(), - clone)); - preventCachingIfReferenceIsNotLoaded(record); - registerChangeRecord(clone, orig, descriptor, record); - } - - private void preventCachingIfReferenceIsNotLoaded(ChangeRecord changeRecord) { - final Object newValue = changeRecord.getNewValue(); - if (newValue != null && contains(newValue) && isLoaded(newValue) != LoadState.LOADED) { - changeRecord.preventCaching(); - } - } - - private void registerChangeRecord(Object clone, Object orig, Descriptor descriptor, ChangeRecord record) { - ObjectChangeSet chSet = uowChangeSet.getExistingObjectChanges(orig); - if (chSet == null) { - chSet = ChangeSetFactory.createObjectChangeSet(orig, clone, descriptor); - uowChangeSet.addObjectChangeSet(chSet); - } - chSet.addChangeRecord(record); + * @param cls Return type of the original + * @param identifier Instance identifier + * @param descriptor Repository descriptor + * @return Original object managed by this UoW or {@code null} if this UoW doesn't contain a matching instance + */ + public T getManagedOriginal(Class cls, Object identifier, Descriptor descriptor) { + final T clone = getManagedClone(cls, identifier, descriptor); + return clone != null ? cls.cast(cloneToOriginals.get(clone)) : null; } /** - * Merge the changes from this Unit of Work's change set into the server session. + * Check if this UnitOfWork contains this original entity. This method is used by the CloneBuilder so it does not + * have to clone already managed referenced objects. + * + * @param entity The original entity. + * @return True if the original is managed in this UnitOfWork. */ - private void mergeChangesIntoParent() { - if (hasChanges()) { - mergeManager.mergeChangesFromChangeSet(uowChangeSet); - } - evictPossiblyUpdatedReferencesFromCache(); - } - - private void evictPossiblyUpdatedReferencesFromCache() { - cloneToOriginals.forEach((clone, orig) -> { - if (orig == null && !deletedObjects.containsKey(clone)) { - removeObjectFromCache(clone, getDescriptor(clone).getSingleContext().orElse(null)); - } - }); + boolean containsOriginal(Object entity) { + return entity != null && cloneToOriginals.containsValue(entity); } - @Override - public T mergeDetached(T entity, Descriptor descriptor) { - Objects.requireNonNull(entity); - Objects.requireNonNull(descriptor); - - final Object id = getIdentifier(entity); - if (!storage.contains(id, entity.getClass(), descriptor)) { - registerNewObject(entity, descriptor); - return entity; - } else { - if (isIndividualManaged(id, entity) && !isSameType(id, entity)) { - throw individualAlreadyManaged(id); + /** + * Finds clone of the specified original. + * + * @param original The original object whose clone we are looking for + * @return The clone or null, if there is none + */ + public Object getCloneForOriginal(Object original) { + for (Entry entry : cloneToOriginals.entrySet()) { + // We use IdentityMap, so we can use == + if (entry.getValue() == original) { + return entry.getKey(); } - return mergeDetachedInternal(entity, descriptor); } + return null; } - private boolean isSameType(Object id, Object entity) { - final Class mergedType = entity.getClass(); - final Object managed = keysToClones.containsKey(id) ? keysToClones.get(id) : newObjectsKeyToClone.get(id); - return managed != null && managed.getClass().isAssignableFrom(mergedType); + public boolean hasChanges() { + return hasChanges || hasDeleted || hasNew; } - private T mergeDetachedInternal(T entity, Descriptor descriptor) { - assert entity != null; - final IdentifiableEntityType et = (IdentifiableEntityType) entityType(entity.getClass()); - final URI idUri = EntityPropertiesUtils.getIdentifier(entity, et); - - final T clone = getInstanceForMerge(idUri, et, descriptor); - try { - // Merge only the changed attributes - ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(clone, entity, descriptor); - // Have to check for inferred attribute changes before the actual merge - changeManager.calculateChanges(chSet); - chSet = processInferredValueChanges(chSet); - if (chSet.hasChanges()) { - et.getLifecycleListenerManager().invokePreUpdateCallbacks(clone); - final DetachedInstanceMerger merger = new DetachedInstanceMerger(this); - merger.mergeChangesFromDetachedToManagedInstance(chSet, descriptor); - for (ChangeRecord record : chSet.getChanges()) { - AttributeModificationValidator.verifyCanModify(record.getAttribute()); - preventCachingIfReferenceIsNotLoaded(record); - storage.merge(clone, (FieldSpecification) record.getAttribute(), descriptor); - } - et.getLifecycleListenerManager().invokePostUpdateCallbacks(clone); - uowChangeSet.addObjectChangeSet(copyChangeSet(chSet, getOriginal(clone), clone, descriptor)); - } - } catch (OWLEntityExistsException e) { - unregisterObject(clone); - throw e; - } - evictAfterMerge(et, idUri, descriptor); - setHasChanges(); - checkForIndirectObjects(clone); - return et.getJavaType().cast(clone); + void setHasChanges() { + this.hasChanges = true; } - private T getInstanceForMerge(URI identifier, EntityType et, Descriptor descriptor) { - if (keysToClones.containsKey(identifier)) { - return (T) keysToClones.get(identifier); + void preventCachingIfReferenceIsNotLoaded(ChangeRecord changeRecord) { + final Object newValue = changeRecord.getNewValue(); + if (newValue != null && contains(newValue) && isLoaded(newValue) != LoadState.LOADED) { + changeRecord.preventCaching(); } - final LoadingParameters params = new LoadingParameters<>(et.getJavaType(), identifier, descriptor, true); - T original = storage.find(params); - assert original != null; - - return (T) registerExistingObject(original, descriptor); } - private ObjectChangeSet processInferredValueChanges(ObjectChangeSet changeSet) { + protected ObjectChangeSet processInferredValueChanges(ObjectChangeSet changeSet) { if (getConfiguration().is(JOPAPersistenceProperties.IGNORE_INFERRED_VALUE_REMOVAL_ON_MERGE)) { - final ObjectChangeSet copy = ChangeSetFactory.createObjectChangeSet(changeSet.getChangedObject(), changeSet.getCloneObject(), changeSet.getEntityDescriptor()); + final ObjectChangeSet copy = ChangeSetFactory.createObjectChangeSet(changeSet.getOriginal(), changeSet.getClone(), changeSet.getDescriptor()); changeSet.getChanges().stream().filter(chr -> !(chr.getAttribute().isInferred() && - inferredAttributeChangeValidator.isInferredValueRemoval(changeSet.getCloneObject(), changeSet.getChangedObject(), + inferredAttributeChangeValidator.isInferredValueRemoval(changeSet.getClone(), changeSet.getOriginal(), (FieldSpecification) chr.getAttribute(), - changeSet.getEntityDescriptor()))).forEach(copy::addChangeRecord); + changeSet.getDescriptor()))).forEach(copy::addChangeRecord); return copy; } else { changeSet.getChanges().stream().filter(chr -> chr.getAttribute().isInferred()).forEach( - chr -> inferredAttributeChangeValidator.validateChange(changeSet.getCloneObject(), changeSet.getChangedObject(), + chr -> inferredAttributeChangeValidator.validateChange(changeSet.getClone(), changeSet.getOriginal(), (FieldSpecification) chr.getAttribute(), - changeSet.getEntityDescriptor())); + changeSet.getDescriptor())); return changeSet; } } - private static ObjectChangeSet copyChangeSet(ObjectChangeSet changeSet, Object original, Object clone, - Descriptor descriptor) { - final ObjectChangeSet newChangeSet = ChangeSetFactory.createObjectChangeSet(original, clone, descriptor); - changeSet.getChanges().forEach(newChangeSet::addChangeRecord); - return newChangeSet; - } - - private void evictAfterMerge(EntityType et, URI identifier, Descriptor descriptor) { - if (cacheManager.contains(et.getJavaType(), identifier, descriptor)) { - cacheManager.evict(et.getJavaType(), identifier, descriptor.getSingleContext().orElse(null)); + protected T getInstanceForMerge(URI identifier, EntityType et, Descriptor descriptor) { + if (keysToClones.containsKey(identifier)) { + T managed = (T) keysToClones.get(identifier); + et.getFieldSpecifications().stream().filter(fs -> fs.getFetchType() == FetchType.LAZY).forEach(fs -> { + final Object fieldValue = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), managed); + if (fieldValue instanceof LazyLoadingProxy proxy) { + proxy.triggerLazyLoading(); + } + }); + return managed; } - getMetamodel().getReferringTypes(et.getJavaType()).forEach(cacheManager::evict); - } - - private void registerEntityWithPersistenceContext(Object entity) { - Aspects.aspectOf(BeanListenerAspect.class).register(entity, this); - } - - private static void deregisterEntityFromPersistenceContext(Object entity) { - Aspects.aspectOf(BeanListenerAspect.class).deregister(entity); - } - - @Override - public NamedQueryManager getNamedQueryManager() { - return parent.getNamedQueryManager(); - } - - @Override - public ResultSetMappingManager getResultSetMappingManager() { - return parent.getResultSetMappingManager(); - } + final LoadingParameters params = new LoadingParameters<>(et.getJavaType(), identifier, descriptor, true); + T original = storage.find(params); + assert original != null; - @Override - public Object registerExistingObject(Object entity, Descriptor descriptor) { - return registerExistingObject(entity, descriptor, Collections.emptyList()); + return (T) registerExistingObject(original, new CloneRegistrationDescriptor(descriptor).allEager(true)); } - @Override - public Object registerExistingObject(Object entity, Descriptor descriptor, List> postClone) { - if (entity == null) { - return null; + protected void evictAfterMerge(EntityType et, URI identifier, Descriptor descriptor) { + if (getLiveObjectCache().contains(et.getJavaType(), identifier, descriptor)) { + getLiveObjectCache().evict(et.getJavaType(), identifier, descriptor.getSingleContext().orElse(null)); } - if (cloneToOriginals.containsValue(entity)) { - return getCloneForOriginal(entity); - } - final CloneConfiguration cloneConfig = new CloneConfiguration(descriptor); - postClone.forEach(cloneConfig::addPostRegisterHandler); - Object clone = cloneBuilder.buildClone(entity, cloneConfig); - assert clone != null; - registerClone(clone, entity, descriptor); - postClone.forEach(c -> c.accept(clone)); - return clone; - } - - private void registerClone(Object clone, Object original, Descriptor descriptor) { - cloneToOriginals.put(clone, original); - final Object identifier = EntityPropertiesUtils.getIdentifier(clone, getMetamodel()); - keysToClones.put(identifier, clone); - final InstanceDescriptor instanceDesc = identifier != null ? InstanceDescriptorFactory.create(clone, (EntityType) entityType(clone.getClass())) : InstanceDescriptorFactory.createAllLoaded(clone, (EntityType) entityType(clone.getClass())); - instanceDescriptors.put(clone, instanceDesc); - registerEntityWithPersistenceContext(clone); - registerEntityWithOntologyContext(clone, descriptor); + getMetamodel().getReferringTypes(et.getJavaType()).forEach(getLiveObjectCache()::evict); } - /** - * Release this Unit of Work. Releasing an active Unit of Work with uncommitted changes causes all pending changes - * to be discarded. - */ - @Override - public void release() { - clear(); - storage.close(); - this.isActive = false; - LOG.debug("UnitOfWork released."); + protected static ObjectChangeSet copyChangeSet(ObjectChangeSet changeSet, Object original, Object clone, + Descriptor descriptor) { + final ObjectChangeSet newChangeSet = ChangeSetFactory.createObjectChangeSet(original, clone, descriptor); + changeSet.getChanges().forEach(newChangeSet::addChangeRecord); + return newChangeSet; } @Override @@ -764,28 +644,22 @@ public void refreshObject(T object) { if (original == null) { throw new EntityNotFoundException("Entity " + object + " no longer exists in the repository."); } - T source = (T) cloneBuilder.buildClone(original, new CloneConfiguration(descriptor)); + T source = (T) cloneBuilder.buildClone(original, CloneConfiguration.withDescriptor(descriptor)); final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(source, object, descriptor); - changeManager.calculateChanges(chSet); + changeCalculator.calculateChanges(chSet); new RefreshInstanceMerger(indirectWrapperHelper).mergeChanges(chSet); revertTransactionalChanges(object, descriptor, chSet); registerClone(object, original, descriptor); + loadStateRegistry.put(object, LoadStateDescriptorFactory.createAllLoaded(object, et)); et.getLifecycleListenerManager().invokePostLoadCallbacks(object); } finally { connection.close(); } } - private void ensureManaged(T object) { - if (!isObjectManaged(object)) { - throw new IllegalArgumentException("Object not managed by this persistence context."); - } - } - private void revertTransactionalChanges(T object, Descriptor descriptor, ObjectChangeSet chSet) { for (ChangeRecord change : chSet.getChanges()) { - storage.merge(object, (FieldSpecification) change.getAttribute(), - descriptor.getAttributeDescriptor(change.getAttribute())); + storage.merge(object, (FieldSpecification) change.getAttribute(), descriptor.getAttributeDescriptor(change.getAttribute())); } } @@ -794,38 +668,27 @@ public void registerNewObject(Object entity, Descriptor descriptor) { Objects.requireNonNull(entity); Objects.requireNonNull(descriptor); - registerNewObjectInternal(entity, descriptor); - } - - /** - * Registers the specified entity for persist in this Unit of Work. - * - * @param entity The entity to register - * @param descriptor Entity descriptor, specifying optionally contexts into which the entity will be persisted - */ - private void registerNewObjectInternal(Object entity, Descriptor descriptor) { final IdentifiableEntityType eType = entityType(entity.getClass()); eType.getLifecycleListenerManager().invokePrePersistCallbacks(entity); - Object id = getIdentifier(entity); - if (id == null) { - EntityPropertiesUtils.verifyIdentifierIsGenerated(entity, eType); - } - verifyCanPersist(id, entity, eType, descriptor); - storage.persist(id, entity, descriptor); - if (id == null) { - // If the ID was null, extract it from the entity. It is present now - id = getIdentifier(entity); - } + Object id = initEntityIdentifier(entity, (EntityType) eType); assert id != null; + verifyCanPersist(id, entity, eType, descriptor); // Original is null until commit newObjectsCloneToOriginal.put(entity, null); - registerEntityWithPersistenceContext(entity); registerEntityWithOntologyContext(entity, descriptor); - instanceDescriptors.put(entity, InstanceDescriptorFactory.createAllLoaded(entity, (EntityType) eType)); + loadStateRegistry.put(entity, LoadStateDescriptorFactory.createAllLoaded(entity, (EntityType) eType)); newObjectsKeyToClone.put(id, entity); - checkForIndirectObjects(entity); this.hasNew = true; - eType.getLifecycleListenerManager().invokePostPersistCallbacks(entity); + } + + private Object initEntityIdentifier(Object entity, EntityType et) { + Object id = getIdentifier(entity); + if (id == null) { + EntityPropertiesUtils.verifyIdentifierIsGenerated(entity, et); + id = storage.generateIdentifier(et); + EntityPropertiesUtils.setIdentifier(id, entity, et); + } + return id; } private void verifyCanPersist(Object id, Object instance, EntityType et, Descriptor descriptor) { @@ -833,35 +696,19 @@ private void verifyCanPersist(Object id, Object instance, EntityType et, Desc throw individualAlreadyManaged(id); } if (storage.contains(id, instance.getClass(), descriptor)) { - throw new OWLEntityExistsException( - "Individual " + id + " of type " + et.getIRI() + " already exists in storage."); + throw new OWLEntityExistsException("Individual " + id + " of type " + et.getIRI() + " already exists in storage."); } } private boolean isIndividualManaged(Object identifier, Object entity) { - return keysToClones.containsKey(identifier) || - newObjectsKeyToClone.containsKey(identifier) && !cloneMapping.contains(entity); + return keysToClones.containsKey(identifier) || newObjectsKeyToClone.containsKey(identifier) && !cloneMapping.contains(entity); } - @Override - public void removeObject(Object entity) { - assert entity != null; - ensureManaged(entity); - - final IdentifiableEntityType et = entityType(entity.getClass()); - et.getLifecycleListenerManager().invokePreRemoveCallbacks(entity); - final Object primaryKey = getIdentifier(entity); - final Descriptor descriptor = getDescriptor(entity); - if (hasNew && newObjectsCloneToOriginal.containsKey(entity)) { - unregisterObject(entity); - newObjectsKeyToClone.remove(primaryKey); - } else { - deletedObjects.put(entity, entity); - this.hasDeleted = true; + void ensureManaged(T object) { + if (!isObjectManaged(object)) { + throw new IllegalArgumentException("Object not managed by this persistence context."); } - storage.remove(primaryKey, et.getJavaType(), descriptor); - et.getLifecycleListenerManager().invokePostRemoveCallbacks(entity); } @Override @@ -873,11 +720,7 @@ public void restoreRemovedObject(Object entity) { storage.persist(id, entity, getDescriptor(entity)); } - /** - * Remove the registered object from this Unit of Work. - * - * @param object Clone of the original object - */ + @Override public void unregisterObject(Object object) { if (object == null) { return; @@ -892,24 +735,18 @@ public void unregisterObject(Object object) { if (original != null) { cloneBuilder.removeVisited(original, repoMap.getEntityDescriptor(object)); } - removeIndirectWrappers(object); - deregisterEntityFromPersistenceContext(object); unregisterEntityFromOntologyContext(object); } - @Override - public boolean shouldReleaseAfterCommit() { - return shouldReleaseAfterCommit; - } - - public void setShouldClearAfterCommit(boolean shouldClearCache) { - this.shouldClearCacheAfterCommit = shouldClearCache; - } + private void unregisterEntityFromOntologyContext(Object entity) { + assert entity != null; - public void setEntityManager(AbstractEntityManager entityManager) { - this.entityManager = entityManager; - // TODO This is a temporary workaround, configuration should be provided in constructor - this.configuration = entityManager.getConfiguration(); + final Descriptor descriptor = repoMap.getEntityDescriptor(entity); + if (descriptor == null) { + throw new OWLPersistenceException("Fatal error, unable to find descriptor for entity " + stringify(entity)); + } + repoMap.remove(descriptor, entity); + repoMap.removeEntityToRepository(entity); } @Override @@ -924,45 +761,32 @@ public MetamodelImpl getMetamodel() { return parent.getMetamodel(); } - private IdentifiableEntityType entityType(Class cls) { - return getMetamodel().entity(cls); - } - @Override public boolean isEntityType(Class cls) { - return parent.isEntityType(cls); + return getMetamodel().isEntityType(cls); } @Override public boolean isInTransaction() { - return entityManager != null && entityManager.getTransaction().isActive(); + return transactionActive; } - /** - * Returns {@code true} if this UoW is currently committing changes. - * - * @return Whether this UoW is in the commit phase - */ + @Override public boolean isInCommit() { return inCommit; } @Override - public void loadEntityField(T entity, Field field) { + public Object loadEntityField(T entity, FieldSpecification fieldSpec) { Objects.requireNonNull(entity); - Objects.requireNonNull(field); + Objects.requireNonNull(fieldSpec); + final Field field = fieldSpec.getJavaField(); assert field.getDeclaringClass().isAssignableFrom(entity.getClass()); final Descriptor entityDescriptor = getDescriptor(entity); - if (!instanceDescriptors.containsKey(entity)) { - throw new OWLPersistenceException( - "Unable to find repository identifier for entity " + entity + ". Is it managed by this UoW?"); - } - final InstanceDescriptor instanceDescriptor = instanceDescriptors.get(entity); - final FieldSpecification fieldSpec = entityType((Class) entity.getClass()) - .getFieldSpecification(field.getName()); - if (instanceDescriptor.isLoaded(fieldSpec) == LoadState.LOADED) { - return; + final LoadStateDescriptor loadStateDescriptor = loadStateRegistry.get(entity); + if (loadStateDescriptor.isLoaded(fieldSpec) == LoadState.LOADED) { + return EntityPropertiesUtils.getFieldValue(field, entity); } storage.loadFieldValue(entity, fieldSpec, entityDescriptor); @@ -974,7 +798,25 @@ public void loadEntityField(T entity, Field field) { final Descriptor fieldDescriptor = getFieldDescriptor(entity, field, entityDescriptor); final Object clone = cloneLoadedFieldValue(entity, field, fieldDescriptor, orig); EntityPropertiesUtils.setFieldValue(field, entity, clone); - instanceDescriptors.get(entity).setLoaded(fieldSpec, LoadState.LOADED); + loadStateDescriptor.setLoaded((FieldSpecification) fieldSpec, LoadState.LOADED); + return clone; + } + + /** + * Gets basic object info for logging. + *

    + * This works around using {@link Object#toString()} for entities, which could inadvertently trigger lazy field + * fetching and cause an infinite field loading loop. + * + * @param object Object to stringify + * @return String info about the specified object + */ + public String stringify(Object object) { + assert object != null; + return isEntityType(object.getClass()) ? + (object.getClass().getSimpleName() + IdentifierTransformer.stringifyIri( + EntityPropertiesUtils.getIdentifier(object, getMetamodel()))) : + object.toString(); } private Descriptor getFieldDescriptor(T entity, Field field, Descriptor entityDescriptor) { @@ -999,11 +841,18 @@ private Object cloneLoadedFieldValue(T entity, Field field, final Descriptor return clone; } + @Override + public void putObjectIntoCache(Object identifier, Object entity, Descriptor descriptor) { + final LoadStateDescriptor loadStateDescriptor = loadStateRegistry.get(entity); + assert loadStateDescriptor != null; + getLiveObjectCache().add(identifier, entity, new Descriptors(descriptor, loadStateDescriptor)); + } + @Override public void removeObjectFromCache(Object toRemove, URI context) { Objects.requireNonNull(toRemove); - cacheManager.evict(toRemove.getClass(), getIdentifier(toRemove), context); + getLiveObjectCache().evict(MetamodelUtils.getEntityClass(toRemove.getClass()), getIdentifier(toRemove), context); } @Override @@ -1031,113 +880,24 @@ 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 instanceDescriptors.containsKey(entity) ? instanceDescriptors.get(entity).isLoaded(fs) : - LoadState.UNKNOWN; + return loadStateRegistry.contains(entity) ? loadStateRegistry.get(entity).isLoaded(fs) : LoadState.UNKNOWN; } @Override public LoadState isLoaded(Object entity) { Objects.requireNonNull(entity); - return instanceDescriptors.containsKey(entity) ? instanceDescriptors.get(entity).isLoaded() : LoadState.UNKNOWN; + return loadStateRegistry.contains(entity) ? loadStateRegistry.get(entity).isLoaded() : LoadState.UNKNOWN; } public SparqlQueryFactory sparqlQueryFactory() { return queryFactory; } - public CriteriaBuilder criteriaFactory() { - return criteriaFactory; - } - - /** - * Check if the specified entity contains a collection. If so, replace it with its indirect representation so that - * changes in that collection can be tracked. - * - * @param entity The entity to check - */ - private void checkForIndirectObjects(Object entity) { - assert entity != null; - final EntityType et = entityType(entity.getClass()); - for (FieldSpecification fieldSpec : et.getFieldSpecifications()) { - setIndirectObjectIfPresent(entity, fieldSpec.getJavaField()); - } - } - - /** - * Create and set indirect collection on the specified entity field. - *

    - * If the specified field is of Collection type and it is not already an indirect collection, create new one and set - * it as the value of the specified field on the specified entity. - * - * @param entity The entity collection will be set on - * @param field The field to set - * @throws IllegalArgumentException Reflection - */ - private void setIndirectObjectIfPresent(Object entity, Field field) { - assert entity != null; - assert field != null; - - final Object value = EntityPropertiesUtils.getFieldValue(field, entity); - if (value instanceof IndirectWrapper) { - return; - } - if (IndirectWrapperHelper.requiresIndirectWrapper(value)) { - EntityPropertiesUtils - .setFieldValue(field, entity, indirectWrapperHelper.createIndirectWrapper(value, entity, field)); - } - } - - /** - * Creates an indirect collection, which wraps the specified collection instance and propagates changes to the - * persistence context. - * - * @param collection Collection to be proxied - * @param owner Collection owner instance - * @param field Field filled with the collection - * @return Indirect collection - */ - public Object createIndirectCollection(Object collection, Object owner, Field field) { - return indirectWrapperHelper.createIndirectWrapper(collection, owner, field); - } - - /** - * Removes {@link IndirectWrapper} instances from the specified entity (if present). - * - * @param entity The entity to remove indirect wrappers from - */ - private void removeIndirectWrappers(Object entity) { - assert entity != null; - final EntityType et = entityType(entity.getClass()); - for (FieldSpecification fs : et.getFieldSpecifications()) { - final Object value = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), entity); - if (value instanceof IndirectWrapper) { - IndirectWrapper indirectWrapper = (IndirectWrapper) value; - EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, indirectWrapper.unwrap()); - } - } - } - - void putObjectIntoCache(Object identifier, Object entity, Descriptor descriptor) { - cacheManager.add(identifier, entity, descriptor); - } - - private Object getIdentifier(Object entity) { - return EntityPropertiesUtils.getIdentifier(entity, getMetamodel()); - } - - private void unregisterEntityFromOntologyContext(Object entity) { - assert entity != null; - - final Descriptor descriptor = repoMap.getEntityDescriptor(entity); - if (descriptor == null) { - throw new OWLPersistenceException("Fatal error, unable to find descriptor for entity " + entity); - } - - repoMap.remove(descriptor, entity); - repoMap.removeEntityToRepository(entity); + public CriteriaBuilder getCriteriaBuilder() { + return parent.getCriteriaBuilder(); } - private void registerEntityWithOntologyContext(Object entity, Descriptor descriptor) { + void registerEntityWithOntologyContext(Object entity, Descriptor descriptor) { assert descriptor != null; assert entity != null; @@ -1145,30 +905,18 @@ private void registerEntityWithOntologyContext(Object entity, Descriptor descrip repoMap.addEntityToRepository(entity, descriptor); } - private boolean isInRepository(Descriptor descriptor, Object entity) { - assert descriptor != null; - assert entity != null; - - return repoMap.contains(descriptor, entity); - } - - private Descriptor getDescriptor(Object entity) { + Descriptor getDescriptor(Object entity) { assert entity != null; final Descriptor descriptor = repoMap.getEntityDescriptor(entity); if (descriptor == null) { - throw new OWLPersistenceException("Unable to find descriptor of entity " + entity + " in this UoW!"); + throw new OWLPersistenceException("Unable to find descriptor of entity " + stringify(entity) + " in this UoW!"); } return descriptor; } - private void storageCommit() { - try { - storage.commit(); - } catch (OWLPersistenceException e) { - entityManager.removeCurrentPersistenceContext(); - throw e; - } + public LoadStateDescriptorRegistry getLoadStateRegistry() { + return loadStateRegistry; } @Override @@ -1179,7 +927,13 @@ public T unwrap(Class cls) { return storage.unwrap(cls); } - public SparqlQueryFactory getQueryFactory() { - return queryFactory; + protected void markCloneForDeletion(Object entity, Object identifier) { + if (hasNew && newObjectsCloneToOriginal.containsKey(entity)) { + unregisterObject(entity); + newObjectsKeyToClone.remove(identifier); + } else { + deletedObjects.put(entity, entity); + this.hasDeleted = true; + } } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWork.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWork.java new file mode 100644 index 000000000..8fa4216ce --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWork.java @@ -0,0 +1,267 @@ +package cz.cvut.kbss.jopa.sessions; + +import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException; +import cz.cvut.kbss.jopa.model.LoadState; +import cz.cvut.kbss.jopa.model.Manageable; +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +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.IndirectWrapper; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; +import cz.cvut.kbss.jopa.sessions.validator.AttributeModificationValidator; +import cz.cvut.kbss.jopa.utils.Configuration; +import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; + +import java.lang.reflect.Field; +import java.net.URI; + +public class ChangeTrackingUnitOfWork extends AbstractUnitOfWork { + + public ChangeTrackingUnitOfWork(AbstractSession parent, Configuration configuration) { + super(parent, configuration); + } + + @Override + protected T readObjectInternal(Class cls, Object identifier, Descriptor descriptor) { + final T clone = super.readObjectInternal(cls, identifier, descriptor); + if (clone != null) { + checkForIndirectObjects(clone); + } + return clone; + } + + /** + * Check if the specified entity contains a collection. If so, replace it with its indirect representation so that + * changes in that collection can be tracked. + * + * @param entity The entity to check + */ + private void checkForIndirectObjects(Object entity) { + assert entity != null; + final EntityType et = entityType(entity.getClass()); + for (FieldSpecification fieldSpec : et.getFieldSpecifications()) { + setIndirectObjectIfPresent(entity, fieldSpec.getJavaField()); + } + } + + /** + * Create and set indirect collection on the specified entity field. + *

    + * If the specified field is of Collection type, and it is not already an indirect collection, create new one and + * set it as the value of the specified field on the specified entity. + * + * @param entity The entity collection will be set on + * @param field The field to set + * @throws IllegalArgumentException Reflection + */ + private void setIndirectObjectIfPresent(Object entity, Field field) { + assert entity != null; + assert field != null; + + final Object value = EntityPropertiesUtils.getFieldValue(field, entity); + if (value instanceof IndirectWrapper) { + return; + } + if (IndirectWrapperHelper.requiresIndirectWrapper(value)) { + EntityPropertiesUtils.setFieldValue(field, entity, indirectWrapperHelper.createIndirectWrapper(value, entity, field)); + } + } + + /** + * Creates an indirect collection, which wraps the specified collection instance and propagates changes to the + * persistence context. + * + * @param collection Collection to be proxied + * @param owner Collection owner instance + * @param field Field filled with the collection + * @return Indirect collection + */ + public Object createIndirectCollection(Object collection, Object owner, Field field) { + return indirectWrapperHelper.createIndirectWrapper(collection, owner, field); + } + + /** + * If there are any changes, commit them to the ontology. + */ + void commitToStorage() { + if (this.hasNew || this.hasChanges || this.hasDeleted) { + persistNewObjects(); + calculateChanges(); + } + validateIntegrityConstraints(); + storage.commit(); + } + + @Override + protected void detachAllManagedInstances() { + cloneMapping.forEach(instance -> { + removeIndirectWrappersAndProxies(instance); + deregisterEntityFromPersistenceContext(instance); + }); + newObjectsCloneToOriginal.keySet().forEach(this::removeIndirectWrappersAndProxies); + } + + /** + * Removes {@link IndirectWrapper} and {@link LazyLoadingProxy} instances from the specified entity (if present). + * + * @param entity The entity to remove indirect wrappers from + */ + private void removeIndirectWrappersAndProxies(Object entity) { + assert entity != null; + final EntityType et = entityType(entity.getClass()); + for (FieldSpecification fs : et.getFieldSpecifications()) { + final Object value = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), entity); + if (value instanceof IndirectWrapper indirectWrapper) { + EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, indirectWrapper.unwrap()); + } else if (value instanceof LazyLoadingProxy lazyLoadingProxy) { + EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, lazyLoadingProxy.unwrap()); + } + } + } + + @Override + void registerClone(Object clone, Object original, Descriptor descriptor) { + super.registerClone(clone, original, descriptor); + attachPersistenceContextToEntity(clone); + } + + private void attachPersistenceContextToEntity(Object entity) { + if (isInCommit()) { + return; + } + assert entity instanceof Manageable; + ((Manageable) entity).setPersistenceContext(this); + } + + private static void deregisterEntityFromPersistenceContext(Object entity) { + if (!(entity instanceof Manageable)) { + return; + } + ((Manageable) entity).setPersistenceContext(null); + } + + @Override + public void attributeChanged(Object entity, Field f) { + final IdentifiableEntityType et = entityType((Class) entity.getClass()); + final FieldSpecification fieldSpec = et.getFieldSpecification(f.getName()); + attributeChanged(entity, fieldSpec); + } + + @Override + public void attributeChanged(Object entity, FieldSpecification fieldSpec) { + if (!isInTransaction()) { + throw new IllegalStateException("This unit of work is not in a transaction."); + } + final Descriptor descriptor = getDescriptor(entity); + final IdentifiableEntityType et = entityType((Class) entity.getClass()); + final Object original = getOriginal(entity); + if (fieldSpec.isInferred() && original != null) { + inferredAttributeChangeValidator.validateChange(entity, getOriginal(entity), (FieldSpecification) fieldSpec, descriptor); + } + et.getLifecycleListenerManager().invokePreUpdateCallbacks(entity); + storage.merge(entity, (FieldSpecification) fieldSpec, descriptor); + createAndRegisterChangeRecord(entity, fieldSpec, descriptor); + setHasChanges(); + setIndirectObjectIfPresent(entity, fieldSpec.getJavaField()); + et.getLifecycleListenerManager().invokePostUpdateCallbacks(entity); + ((LoadStateDescriptor) loadStateRegistry.get(entity)).setLoaded(fieldSpec, LoadState.LOADED); + } + + private void createAndRegisterChangeRecord(Object clone, FieldSpecification fieldSpec, + Descriptor descriptor) { + final Object orig = getOriginal(clone); + if (orig == null) { + return; + } + final ChangeRecord record = new ChangeRecord(fieldSpec, EntityPropertiesUtils.getFieldValue(fieldSpec.getJavaField(), clone)); + preventCachingIfReferenceIsNotLoaded(record); + registerChangeRecord(clone, orig, descriptor, record); + } + + private void registerChangeRecord(Object clone, Object orig, Descriptor descriptor, ChangeRecord record) { + ObjectChangeSet chSet = uowChangeSet.getExistingObjectChanges(orig); + if (chSet == null) { + chSet = ChangeSetFactory.createObjectChangeSet(orig, clone, descriptor); + uowChangeSet.addObjectChangeSet(chSet); + } + chSet.addChangeRecord(record); + } + + T mergeDetachedInternal(T entity, Descriptor descriptor) { + assert entity != null; + final IdentifiableEntityType et = (IdentifiableEntityType) entityType(entity.getClass()); + final URI idUri = EntityPropertiesUtils.getIdentifier(entity, et); + + final T clone = getInstanceForMerge(idUri, et, descriptor); + try { + ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(clone, entity, descriptor); + // Merge only the changed attributes + changeCalculator.calculateChanges(chSet); + // Have to check for inferred attribute changes before the actual merge + chSet = processInferredValueChanges(chSet); + if (chSet.hasChanges()) { + et.getLifecycleListenerManager().invokePreUpdateCallbacks(clone); + final DetachedInstanceMerger merger = new DetachedInstanceMerger(this); + merger.mergeChangesFromDetachedToManagedInstance(chSet, descriptor); + for (ChangeRecord record : chSet.getChanges()) { + AttributeModificationValidator.verifyCanModify(record.getAttribute()); + preventCachingIfReferenceIsNotLoaded(record); + storage.merge(clone, (FieldSpecification) record.getAttribute(), descriptor); + } + et.getLifecycleListenerManager().invokePostUpdateCallbacks(clone); + registerMergeChangeSet(chSet, clone, descriptor); + } + } catch (OWLEntityExistsException e) { + unregisterObject(clone); + throw e; + } + evictAfterMerge(et, idUri, descriptor); + setHasChanges(); + checkForIndirectObjects(clone); + return et.getJavaType().cast(clone); + } + + private void registerMergeChangeSet(ObjectChangeSet mergeChangeSet, T clone, Descriptor descriptor) { + final Object original = getOriginal(clone); + if (uowChangeSet.getExistingObjectChanges(original) != null) { + final ObjectChangeSet existingChSet = uowChangeSet.getExistingObjectChanges(original); + mergeChangeSet.getChanges().forEach(existingChSet::addChangeRecord); + } else { + uowChangeSet.addObjectChangeSet(copyChangeSet(mergeChangeSet, original, clone, descriptor)); + } + } + + @Override + public void registerNewObject(Object entity, Descriptor descriptor) { + super.registerNewObject(entity, descriptor); + checkForIndirectObjects(entity); + } + + @Override + public void unregisterObject(Object object) { + super.unregisterObject(object); + removeIndirectWrappersAndProxies(object); + deregisterEntityFromPersistenceContext(object); + } + + @Override + public void removeObject(Object entity) { + assert entity != null; + ensureManaged(entity); + + final IdentifiableEntityType et = entityType(entity.getClass()); + et.getLifecycleListenerManager().invokePreRemoveCallbacks(entity); + final Object identifier = getIdentifier(entity); + // Get the descriptor before clone is removed + final Descriptor descriptor = getDescriptor(entity); + + markCloneForDeletion(entity, identifier); + storage.remove(identifier, et.getJavaType(), descriptor); + et.getLifecycleListenerManager().invokePostRemoveCallbacks(entity); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/CloneBuilderImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/CloneBuilder.java similarity index 55% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/CloneBuilderImpl.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/CloneBuilder.java index a73235612..a953df52f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/CloneBuilderImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/CloneBuilder.java @@ -18,14 +18,21 @@ package cz.cvut.kbss.jopa.sessions; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.model.LoadState; import cz.cvut.kbss.jopa.model.MultilingualString; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.Identifier; import cz.cvut.kbss.jopa.model.metamodel.Metamodel; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxyFactory; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; +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.CloneConfiguration; +import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; -import cz.cvut.kbss.jopa.utils.IdentifierTransformer; import cz.cvut.kbss.ontodriver.model.LangString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,14 +42,30 @@ import java.math.BigInteger; import java.net.URI; import java.net.URL; -import java.time.*; -import java.util.*; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.Date; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -public class CloneBuilderImpl implements CloneBuilder { +/** + * Builds clones used in transactions for tracking changes. + */ +public class CloneBuilder { - private static final Logger LOG = LoggerFactory.getLogger(CloneBuilderImpl.class); + private static final Logger LOG = LoggerFactory.getLogger(CloneBuilder.class); private static final Set> IMMUTABLE_TYPES = getImmutableTypes(); @@ -51,35 +74,58 @@ public class CloneBuilderImpl implements CloneBuilder { private final Builders builders; - private final UnitOfWorkImpl uow; + private final LazyLoadingProxyFactory lazyLoaderFactory; + + private final AbstractUnitOfWork uow; - public CloneBuilderImpl(UnitOfWorkImpl uow) { + public CloneBuilder(AbstractUnitOfWork uow) { this.uow = uow; this.visitedEntities = new RepositoryMap(); this.builders = new Builders(); + this.lazyLoaderFactory = new LazyLoadingProxyFactory(uow); } - @Override + /** + * Builds clone of the given object. + * + * @param original Object + * @param cloneConfiguration Configuration for the cloning process + * @return The clone + * @throws NullPointerException If {@code original} is {@code null} + */ public Object buildClone(Object original, CloneConfiguration cloneConfiguration) { Objects.requireNonNull(original); Objects.requireNonNull(cloneConfiguration); if (LOG.isTraceEnabled()) { // Normally this is a bad practice, but since stringify could be quite costly, we want to avoid it if possible - LOG.trace("Cloning object {}.", stringify(original)); + LOG.trace("Cloning object {}.", uow.stringify(original)); } return buildCloneImpl(null, null, original, cloneConfiguration); } - @Override + /** + * Builds clone of the given object. + *

    + * This method differs from {@link #buildClone(Object, CloneConfiguration)} in that it accepts another argument + * which represents the owner of the built clone. This is useful in situations when we are cloning attributes + * directly, e.g. when lazily loading a field value. + * + * @param cloneOwner The owner of the created clone + * @param clonedField The field whose value is being cloned + * @param original The original to clone + * @param descriptor Entity descriptor + * @return The clone + * @throws NullPointerException If {@code cloneOwner}, {@code original} or {@code contextUri} is {@code null} + */ public Object buildClone(Object cloneOwner, Field clonedField, Object original, Descriptor descriptor) { if (cloneOwner == null || original == null || descriptor == null) { throw new NullPointerException(); } if (LOG.isTraceEnabled()) { // Normally this is a bad practice, but since stringify could be quite costly, we want to avoid it if possible - LOG.trace("Cloning object {} with owner {}", stringify(original), stringify(cloneOwner)); + LOG.trace("Cloning object {} with owner {}", uow.stringify(original), uow.stringify(cloneOwner)); } - return buildCloneImpl(cloneOwner, clonedField, original, new CloneConfiguration(descriptor)); + return buildCloneImpl(cloneOwner, clonedField, original, CloneConfiguration.withDescriptor(descriptor)); } private Object buildCloneImpl(Object cloneOwner, Field clonedField, Object original, @@ -100,8 +146,10 @@ private Object buildCloneImpl(Object cloneOwner, Field clonedField, Object origi final AbstractInstanceBuilder builder = getInstanceBuilder(original); Object clone = builder.buildClone(cloneOwner, clonedField, original, cloneConfiguration); if (managed) { - // Register visited object before populating attributes to prevent endless cloning cycles + // Register visited object before populating attributes to prevent infinite cloning cycles putVisitedEntity(descriptor, original, clone); + final LoadStateDescriptor loadState = cloneLoadStateDescriptor(original, clone); + uow.getLoadStateRegistry().put(clone, loadState); } if (!builder.populatesAttributes() && !isImmutable(cls)) { populateAttributes(original, clone, cloneConfiguration); @@ -109,6 +157,35 @@ private Object buildCloneImpl(Object cloneOwner, Field clonedField, Object origi return clone; } + private LoadStateDescriptor cloneLoadStateDescriptor(Object original, Object clone) { + if (!uow.getLoadStateRegistry().contains(original)) { + uow.getLoadStateRegistry().put(original, LoadStateDescriptorFactory.createAllUnknown(original, (EntityType) getMetamodel().entity(original.getClass()))); + } + final LoadStateDescriptor origLoadState = uow.getLoadStateRegistry().get(original); + return LoadStateDescriptorFactory.createCopy(clone, origLoadState); + } + + /** + * Builds a clone of the specified entity reference. + *

    + * It is expected that the specified original is an entity, only its identifier is cloned. + * + * @param original Entity + * @param cloneConfiguration Clone configuration + * @return The clone + */ + public Object buildReferenceClone(Object original, CloneConfiguration cloneConfiguration) { + Objects.requireNonNull(original); + Objects.requireNonNull(cloneConfiguration); + assert isTypeManaged(original.getClass()); + + final Class originalClass = original.getClass(); + final EntityType et = getMetamodel().entity(originalClass); + final Object clone = getInstanceBuilder(original).buildClone(null, null, original, cloneConfiguration); + cloneIdentifier(original, clone, et); + return clone; + } + /** * Clone all the attributes of the original and set the clone values. This also means cloning any relationships and * their targets. @@ -116,6 +193,7 @@ private Object buildCloneImpl(Object cloneOwner, Field clonedField, Object origi private void populateAttributes(Object original, Object clone, CloneConfiguration configuration) { final Class originalClass = original.getClass(); final EntityType et = getMetamodel().entity(originalClass); + final LoadStateDescriptor loadState = uow.getLoadStateRegistry().get(clone); // Ensure the identifier is cloned before any other attributes // This prevents problems where circular references between entities lead to clones being registered with null identifier cloneIdentifier(original, clone, et); @@ -125,36 +203,39 @@ private void populateAttributes(Object original, Object clone, CloneConfiguratio } final Field f = fs.getJavaField(); final Object origVal = EntityPropertiesUtils.getFieldValue(f, original); - if (origVal == null) { - continue; - } - final Class origValueClass = origVal.getClass(); Object clonedValue; - if (isImmutable(origValueClass)) { - // The field is an immutable type - clonedValue = origVal; - } else if (IndirectWrapperHelper.requiresIndirectWrapper(origVal)) { - final Descriptor fieldDescriptor = getFieldDescriptor(f, originalClass, configuration.getDescriptor()); - // Collection or Map - clonedValue = getInstanceBuilder(origVal).buildClone(clone, f, origVal, - new CloneConfiguration(fieldDescriptor, - configuration.getPostRegister())); + if (loadState.isLoaded(fs) == LoadState.NOT_LOADED) { + clonedValue = lazyLoaderFactory.createProxy(clone, (FieldSpecification) fs); + } else if (origVal == null) { + continue; } else { - // Otherwise we have a relationship and we need to clone its target as well - if (isOriginalInUoW(origVal)) { - // If the reference is already managed - clonedValue = uow.getCloneForOriginal(origVal); + final Class origValueClass = origVal.getClass(); + if (isImmutable(origValueClass)) { + // The field is an immutable type + clonedValue = origVal; + } else if (IndirectWrapperHelper.requiresIndirectWrapper(origVal)) { + final Descriptor fieldDescriptor = getFieldDescriptor(f, originalClass, configuration.getDescriptor()); + // Collection or Map + clonedValue = getInstanceBuilder(origVal).buildClone(clone, f, origVal, + CloneConfiguration.withDescriptor(fieldDescriptor) + .forPersistenceContext(configuration.isForPersistenceContext()) + .addPostRegisterHandlers(configuration.getPostRegister())); } else { - if (isTypeManaged(origValueClass)) { - final Descriptor fieldDescriptor = - getFieldDescriptor(f, originalClass, configuration.getDescriptor()); - clonedValue = getVisitedEntity(configuration.getDescriptor(), origVal); - if (clonedValue == null) { - clonedValue = uow.registerExistingObject(origVal, fieldDescriptor, - configuration.getPostRegister()); - } + // Otherwise, we have a relationship, and we need to clone its target as well + if (isOriginalInUoW(origVal)) { + // If the reference is already managed + clonedValue = uow.getCloneForOriginal(origVal); } else { - clonedValue = buildClone(origVal, configuration); + if (isTypeManaged(origValueClass)) { + final Descriptor fieldDescriptor = + getFieldDescriptor(f, originalClass, configuration.getDescriptor()); + clonedValue = getVisitedEntity(configuration.getDescriptor(), origVal); + if (clonedValue == null) { + clonedValue = uow.registerExistingObject(origVal, new CloneRegistrationDescriptor(fieldDescriptor).postCloneHandlers(configuration.getPostRegister())); + } + } else { + clonedValue = buildClone(origVal, configuration); + } } } } @@ -201,9 +282,14 @@ static boolean isImmutable(Object object) { return object == null || isImmutable(object.getClass()); } - @Override + /** + * Merges the changes on clone into the original object. + * + * @param changeSet Contains changes to merge + */ public void mergeChanges(ObjectChangeSet changeSet) { - final Object original = changeSet.getChangedObject(); + final Object original = changeSet.getOriginal(); + final LoadStateDescriptor loadStateDescriptor = uow.getLoadStateRegistry().get(original); try { for (ChangeRecord change : changeSet.getChanges()) { Field f = change.getAttribute().getJavaField(); @@ -215,9 +301,10 @@ public void mergeChanges(ObjectChangeSet changeSet) { Object newVal = change.getNewValue(); if (newVal == null) { EntityPropertiesUtils.setFieldValue(f, original, null); - continue; + } else { + getInstanceBuilder(newVal).mergeChanges(f, original, origVal, newVal); } - getInstanceBuilder(newVal).mergeChanges(f, original, origVal, newVal); + loadStateDescriptor.setLoaded((FieldSpecification) change.getAttribute(), LoadState.LOADED); } } catch (SecurityException e) { throw new OWLPersistenceException(e); @@ -255,63 +342,57 @@ Metamodel getMetamodel() { return uow.getMetamodel(); } - @Override + /** + * Resets the clone builder. + *

    + * Especially resets the visited objects cache to make sure all the clones are built from scratch and are not + * affected by the previously built ones. + */ public void reset() { visitedEntities.clear(); } - @Override - public void removeVisited(Object instance, Descriptor descriptor) { - visitedEntities.remove(descriptor, instance); - } - /** - * Gets basic object info for logging. - *

    - * This works around using {@link Object#toString()} for entities, which could inadvertently trigger lazy field - * fetching. + * Removes the specified instance from the clone builder's visited entities cache. * - * @param object Object to stringify - * @return String info about the specified object + * @param instance The instance to remove (original object). + * @param descriptor Instance descriptor */ - private String stringify(Object object) { - assert object != null; - return isTypeManaged(object.getClass()) ? - (object.getClass().getSimpleName() + IdentifierTransformer.stringifyIri( - EntityPropertiesUtils.getIdentifier(object, getMetamodel()))) : - object.toString(); + public void removeVisited(Object instance, Descriptor descriptor) { + visitedEntities.remove(descriptor, instance); } private static Set> getImmutableTypes() { return Stream.of(Boolean.class, - Character.class, - Byte.class, - Short.class, - Integer.class, - Long.class, - Float.class, - Double.class, - BigInteger.class, - BigDecimal.class, - Void.class, - String.class, - URI.class, - URL.class, - LocalDate.class, - LocalTime.class, - LocalDateTime.class, - ZonedDateTime.class, - OffsetDateTime.class, - OffsetTime.class, - ZoneOffset.class, - Instant.class, - Duration.class, - Period.class, - LangString.class).collect(Collectors.toSet()); + Character.class, + Byte.class, + Short.class, + Integer.class, + Long.class, + Float.class, + Double.class, + BigInteger.class, + BigDecimal.class, + Void.class, + String.class, + URI.class, + URL.class, + LocalDate.class, + LocalTime.class, + LocalDateTime.class, + ZonedDateTime.class, + OffsetDateTime.class, + OffsetTime.class, + ZoneOffset.class, + Instant.class, + Duration.class, + Period.class, + LangString.class).collect(Collectors.toSet()); } private final class Builders { private final AbstractInstanceBuilder defaultBuilder; + private final ManagedInstanceBuilder managedInstanceBuilder; private final AbstractInstanceBuilder dateBuilder; private final AbstractInstanceBuilder multilingualStringBuilder; // Lists and Sets @@ -319,9 +400,10 @@ private final class Builders { private AbstractInstanceBuilder mapBuilder; private Builders() { - this.defaultBuilder = new DefaultInstanceBuilder(CloneBuilderImpl.this, uow); - this.dateBuilder = new DateInstanceBuilder(CloneBuilderImpl.this, uow); - this.multilingualStringBuilder = new MultilingualStringInstanceBuilder(CloneBuilderImpl.this, uow); + this.defaultBuilder = new DefaultInstanceBuilder(CloneBuilder.this, uow); + this.managedInstanceBuilder = new ManagedInstanceBuilder(CloneBuilder.this, uow); + this.dateBuilder = new DateInstanceBuilder(CloneBuilder.this, uow); + this.multilingualStringBuilder = new MultilingualStringInstanceBuilder(CloneBuilder.this, uow); } private AbstractInstanceBuilder getBuilder(Object toClone) { @@ -333,14 +415,16 @@ private AbstractInstanceBuilder getBuilder(Object toClone) { } if (toClone instanceof Map) { if (mapBuilder == null) { - this.mapBuilder = new MapInstanceBuilder(CloneBuilderImpl.this, uow); + this.mapBuilder = new MapInstanceBuilder(CloneBuilder.this, uow); } return mapBuilder; } else if (toClone instanceof Collection) { if (collectionBuilder == null) { - this.collectionBuilder = new CollectionInstanceBuilder(CloneBuilderImpl.this, uow); + this.collectionBuilder = new CollectionInstanceBuilder(CloneBuilder.this, uow); } return collectionBuilder; + } else if (isTypeManaged(toClone.getClass())) { + return managedInstanceBuilder; } else { return defaultBuilder; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/CollectionInstanceBuilder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/CollectionInstanceBuilder.java index bb5b31f7d..9985994a3 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/CollectionInstanceBuilder.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/CollectionInstanceBuilder.java @@ -17,10 +17,12 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectCollection; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; import cz.cvut.kbss.jopa.model.annotations.Types; import cz.cvut.kbss.jopa.model.metamodel.CollectionType; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectCollection; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; +import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; import cz.cvut.kbss.jopa.utils.CollectionFactory; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.jopa.utils.MetamodelUtils; @@ -30,9 +32,13 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; /** * Special class for cloning collections. Introduced because some Java collection have no no-argument constructor and @@ -47,7 +53,7 @@ class CollectionInstanceBuilder extends AbstractInstanceBuilder { private static final Class singletonSetClass = Collections.singleton(null).getClass(); private static final Class arrayAsListClass = Arrays.asList(null, null).getClass(); - CollectionInstanceBuilder(CloneBuilderImpl builder, UnitOfWorkImpl uow) { + CollectionInstanceBuilder(CloneBuilder builder, UnitOfWork uow) { super(builder, uow); } @@ -64,8 +70,8 @@ class CollectionInstanceBuilder extends AbstractInstanceBuilder { Object buildClone(Object cloneOwner, Field field, Object collection, CloneConfiguration configuration) { assert collection instanceof Collection; Collection container = (Collection) collection; - if (container instanceof IndirectCollection) { - container = (Collection) ((IndirectCollection) container).unwrap(); + if (container instanceof ChangeTrackingIndirectCollection) { + container = (Collection) ((ChangeTrackingIndirectCollection) container).unwrap(); } if (Collections.emptyList() == container || Collections.emptySet() == container) { return container; @@ -118,12 +124,7 @@ private static Optional> createNewInstance(Class type, int size throw new OWLPersistenceException(e); } catch (IllegalAccessException e) { logConstructorAccessException(ctor, e); - try { - result = (Collection) AccessController.doPrivileged(new PrivilegedInstanceCreator(ctor)); - } catch (PrivilegedActionException ex) { - logPrivilegedConstructorAccessException(ctor, ex); - // Do nothing - } + // Do nothing } return Optional.ofNullable(result); } @@ -144,7 +145,7 @@ private void cloneCollectionContent(Object cloneOwner, Field field, Collection buildInstanceOfSpecialCollection(Object cloneOwner, Field return arrayList; } else if (singletonListClass.isInstance(container) || singletonSetClass.isInstance(container)) { final Object element = container.iterator().next(); - final Object elementClone = CloneBuilderImpl.isImmutable(element) ? element : - cloneCollectionElement(cloneOwner, field, element, configuration); - return singletonListClass.isInstance(container) ? Collections.singletonList(elementClone) : - Collections.singleton(elementClone); + final Object elementClone = CloneBuilder.isImmutable(element) ? element : + cloneCollectionElement(cloneOwner, field, element, configuration); + final Collection result = CollectionFactory.createDefaultCollection(singletonListClass.isInstance(container) ? CollectionType.LIST : CollectionType.SET); + result.add(elementClone); + return result; } else { return null; } @@ -203,8 +205,8 @@ void mergeChanges(Field field, Object target, Object originalValue, Object clone assert cloneValue instanceof Collection; Collection clone = (Collection) cloneValue; - if (clone instanceof IndirectCollection) { - clone = ((IndirectCollection>) clone).unwrap(); + if (clone instanceof ChangeTrackingIndirectCollection) { + clone = ((ChangeTrackingIndirectCollection>) clone).unwrap(); } Collection orig; if (clone == Collections.emptyList() || clone == Collections.emptySet()) { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapper.java index 817a2c5be..bbc75e061 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapper.java @@ -19,9 +19,11 @@ import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.oom.ObjectOntologyMapper; import cz.cvut.kbss.jopa.oom.ObjectOntologyMapperImpl; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.jopa.utils.Wrapper; import cz.cvut.kbss.ontodriver.Connection; @@ -42,7 +44,7 @@ public ConnectionWrapper(Connection connection) { this.connection = connection; } - void setUnitOfWork(UnitOfWorkImpl uow) { + void setUnitOfWork(AbstractUnitOfWork uow) { this.mapper = new ObjectOntologyMapperImpl(uow, connection); } @@ -67,6 +69,10 @@ public void merge(T entity, FieldSpecification fieldSpec, Desc mapper.updateFieldValue(entity, fieldSpec, descriptor); } + public URI generateIdentifier(EntityType et) { + return mapper.generateIdentifier(et); + } + public void persist(Object identifier, T entity, Descriptor descriptor) { final URI idUri = getIdentifierAsUri(identifier); mapper.persistEntity(idUri, entity, descriptor); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DateInstanceBuilder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DateInstanceBuilder.java index c7cb3d393..b34b68dfc 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DateInstanceBuilder.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DateInstanceBuilder.java @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.sessions; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import java.lang.reflect.Field; @@ -24,7 +25,7 @@ class DateInstanceBuilder extends AbstractInstanceBuilder { - DateInstanceBuilder(CloneBuilderImpl builder, UnitOfWorkImpl uow) { + DateInstanceBuilder(CloneBuilder builder, UnitOfWork uow) { super(builder, uow); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DefaultInstanceBuilder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DefaultInstanceBuilder.java index 553b9e54a..8a9db6034 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DefaultInstanceBuilder.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DefaultInstanceBuilder.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.sessions; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,8 +26,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.security.AccessController; -import java.security.PrivilegedActionException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -39,7 +38,7 @@ class DefaultInstanceBuilder extends AbstractInstanceBuilder { private static final Logger LOG = LoggerFactory.getLogger(DefaultInstanceBuilder.class); - DefaultInstanceBuilder(CloneBuilderImpl builder, UnitOfWorkImpl uow) { + DefaultInstanceBuilder(CloneBuilder builder, UnitOfWork uow) { super(builder, uow); } @@ -50,7 +49,7 @@ class DefaultInstanceBuilder extends AbstractInstanceBuilder { */ @Override Object buildClone(Object cloneOwner, Field field, Object original, CloneConfiguration config) { - if (CloneBuilderImpl.isImmutable(original)) { + if (CloneBuilder.isImmutable(original)) { return original; } final Class javaClass = original.getClass(); @@ -76,14 +75,7 @@ Object buildClone(Object cloneOwner, Field field, Object original, CloneConfigur return newInstance; } catch (SecurityException e) { logConstructorAccessException(c, e); - try { - newInstance = AccessController.doPrivileged(new PrivilegedInstanceCreator(c)); - } catch (PrivilegedActionException ex) { - throw new OWLPersistenceException(ex); - } - if (newInstance != null) { - return newInstance; - } + // Do nothing } catch (NoSuchFieldException e) { throw new OWLPersistenceException(e); } @@ -98,11 +90,7 @@ Object buildClone(Object cloneOwner, Field field, Object original, CloneConfigur newInstance = c.newInstance(params); } catch (SecurityException e) { logConstructorAccessException(c, e); - try { - newInstance = AccessController.doPrivileged(new PrivilegedInstanceCreator(c)); - } catch (PrivilegedActionException ex) { - throw new OWLPersistenceException(ex); - } + throw new OWLPersistenceException(e); } } } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { @@ -162,13 +150,7 @@ private static Object buildNewInstanceUsingDefaultConstructor(final Class jav newInstance = c.newInstance((Object[]) null); } catch (SecurityException e) { logConstructorAccessException(c, e); - try { - newInstance = AccessController - .doPrivileged(new PrivilegedInstanceCreator(c)); - } catch (PrivilegedActionException ex) { - logPrivilegedConstructorAccessException(c, ex); - return null; - } + // Do nothing } } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { LOG.trace("Class {} does not have a suitable no-arg constructor.", javaClass); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DetachedInstanceMerger.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DetachedInstanceMerger.java index c5d3baa12..d81571af5 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DetachedInstanceMerger.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/DetachedInstanceMerger.java @@ -18,6 +18,8 @@ package cz.cvut.kbss.jopa.sessions; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; import cz.cvut.kbss.jopa.sessions.merge.DetachedValueMerger; import cz.cvut.kbss.jopa.sessions.merge.ValueMerger; @@ -25,7 +27,7 @@ class DetachedInstanceMerger { private final ValueMerger valueMerger; - DetachedInstanceMerger(UnitOfWorkImpl uow) { + DetachedInstanceMerger(UnitOfWork uow) { this.valueMerger = new DetachedValueMerger(uow); } @@ -38,8 +40,8 @@ class DetachedInstanceMerger { */ Object mergeChangesFromDetachedToManagedInstance(ObjectChangeSet changeSet, Descriptor descriptor) { assert changeSet != null; - assert changeSet.getCloneObject() != null; - final Object target = changeSet.getChangedObject(); + assert changeSet.getClone() != null; + final Object target = changeSet.getOriginal(); assert target != null; for (ChangeRecord change : changeSet.getChanges()) { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/IndirectWrapperHelper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/IndirectWrapperHelper.java index 727064ebb..15fed9128 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/IndirectWrapperHelper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/IndirectWrapperHelper.java @@ -17,11 +17,12 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectList; -import cz.cvut.kbss.jopa.adapters.IndirectMap; -import cz.cvut.kbss.jopa.adapters.IndirectMultilingualString; -import cz.cvut.kbss.jopa.adapters.IndirectSet; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectList; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectMap; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectMultilingualString; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectSet; import cz.cvut.kbss.jopa.model.MultilingualString; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; import java.lang.reflect.Field; import java.util.Collection; @@ -37,9 +38,9 @@ */ class IndirectWrapperHelper { - private final UnitOfWorkImpl uow; + private final UnitOfWork uow; - IndirectWrapperHelper(UnitOfWorkImpl uow) { + IndirectWrapperHelper(UnitOfWork uow) { this.uow = uow; } @@ -54,13 +55,13 @@ class IndirectWrapperHelper { Object createIndirectWrapper(Object wrapped, Object owner, Field field) { assert requiresIndirectWrapper(wrapped); if (wrapped instanceof List) { - return new IndirectList<>(owner, field, uow, (List) wrapped); + return new ChangeTrackingIndirectList<>(owner, field, uow, (List) wrapped); } else if (wrapped instanceof Set) { - return new IndirectSet<>(owner, field, uow, (Set) wrapped); + return new ChangeTrackingIndirectSet<>(owner, field, uow, (Set) wrapped); } else if (wrapped instanceof Map) { - return new IndirectMap<>(owner, field, uow, (Map) wrapped); + return new ChangeTrackingIndirectMap<>(owner, field, uow, (Map) wrapped); } else if (wrapped instanceof MultilingualString) { - return new IndirectMultilingualString(owner, field, uow, (MultilingualString) wrapped); + return new ChangeTrackingIndirectMultilingualString(owner, field, uow, (MultilingualString) wrapped); } else { throw new UnsupportedOperationException("Unsupported wrapped type " + wrapped.getClass()); } @@ -73,6 +74,6 @@ Object createIndirectWrapper(Object wrapped, Object owner, Field field) { * @return {@code true} if an indirect wrapper is used for the specified target */ static boolean requiresIndirectWrapper(Object target) { - return target instanceof Collection || target instanceof MultilingualString || target instanceof Map; + return (!(target instanceof LazyLoadingProxy)) && (target instanceof Collection || target instanceof MultilingualString || target instanceof Map); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ManagedInstanceBuilder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ManagedInstanceBuilder.java new file mode 100644 index 000000000..4d5455d61 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ManagedInstanceBuilder.java @@ -0,0 +1,32 @@ +package cz.cvut.kbss.jopa.sessions; + +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; +import cz.cvut.kbss.jopa.utils.ReflectionUtils; + +import java.lang.reflect.Field; + +/** + * Builds instances of entity types. + *

    + * This builder expects the original's class has a public no-arg constructor. Furthermore, if the configuration + * specifies that the result will be registered in a persistence context, the instance built is not the base Java type + * of the original, but rather the {@link IdentifiableEntityType#getInstantiableJavaType()} result, which is a generated + * subclass whose instances can be attached to the persistence context. + */ +public class ManagedInstanceBuilder extends DefaultInstanceBuilder { + + ManagedInstanceBuilder(CloneBuilder builder, UnitOfWork uow) { + super(builder, uow); + } + + @Override + Object buildClone(Object cloneOwner, Field field, Object original, CloneConfiguration config) { + assert uow.isEntityType(original.getClass()); + final EntityType et = uow.getMetamodel().entity(original.getClass()); + assert et != null; + final Class cls = config.isForPersistenceContext() ? ((IdentifiableEntityType) et).getInstantiableJavaType() : et.getJavaType(); + return ReflectionUtils.instantiateUsingDefaultConstructor(cls); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MapInstanceBuilder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MapInstanceBuilder.java index 141ceeb56..c64bfbbbf 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MapInstanceBuilder.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MapInstanceBuilder.java @@ -17,35 +17,36 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectCollection; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectCollection; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectMap; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; +import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.security.AccessController; -import java.security.PrivilegedActionException; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; class MapInstanceBuilder extends AbstractInstanceBuilder { - private static final Class singletonMapClass = Collections.singletonMap(null, null) - .getClass(); + private static final Class singletonMapClass = Collections.singletonMap(null, null).getClass(); - MapInstanceBuilder(CloneBuilderImpl builder, UnitOfWorkImpl uow) { + MapInstanceBuilder(CloneBuilder builder, UnitOfWork uow) { super(builder, uow); } @Override Object buildClone(Object cloneOwner, Field field, Object original, CloneConfiguration configuration) { Map orig = (Map) original; - if (original instanceof IndirectCollection) { - orig = ((IndirectCollection>) original).unwrap(); + if (original instanceof ChangeTrackingIndirectCollection) { + orig = ((ChangeTrackingIndirectCollection>) original).unwrap(); } if (orig == Collections.emptyMap()) { return orig; @@ -58,26 +59,23 @@ Object buildClone(Object cloneOwner, Field field, Object original, CloneConfigur clone = buildSingletonClone(cloneOwner, field, orig, configuration); } else if (Collections.emptyMap().equals(orig)) { clone = orig; - } - else { + } else { throw new IllegalArgumentException("Unsupported map type " + origCls); } } - clone = (Map) uow.createIndirectCollection(clone, cloneOwner, field); + clone = new ChangeTrackingIndirectMap<>(cloneOwner, field, uow, clone); return clone; } private Map cloneUsingDefaultConstructor(Object cloneOwner, Field field, Class origCls, Map original, CloneConfiguration configuration) { - Map result = createNewInstance(origCls, original.size()); - if (result != null) { - cloneMapContent(cloneOwner, field, original, result, configuration); - } - return result; + Optional> result = createNewInstance(origCls, original.size()); + result.ifPresent(r -> cloneMapContent(cloneOwner, field, original, r, configuration)); + return result.orElse(null); } - private static Map createNewInstance(Class type, int size) { + private static Optional> createNewInstance(Class type, int size) { Map result = null; final Class[] types = {int.class}; Object[] params; @@ -90,7 +88,7 @@ Object buildClone(Object cloneOwner, Field field, Object original, CloneConfigur params = null; } if (c == null) { - return null; + return Optional.empty(); } try { result = (Map) c.newInstance(params); @@ -98,25 +96,19 @@ Object buildClone(Object cloneOwner, Field field, Object original, CloneConfigur throw new OWLPersistenceException(e); } catch (IllegalAccessException e) { logConstructorAccessException(c, e); - try { - result = (Map) AccessController - .doPrivileged(new PrivilegedInstanceCreator(c)); - } catch (PrivilegedActionException ex) { - logPrivilegedConstructorAccessException(c, ex); - // Do nothing - } + // Do nothing } - return result; + return Optional.ofNullable(result); } private Map buildSingletonClone(Object cloneOwner, Field field, Map orig, CloneConfiguration configuration) { Entry e = orig.entrySet().iterator().next(); - Object key = CloneBuilderImpl.isImmutable(e.getKey()) ? e.getKey() : - cloneObject(cloneOwner, field, e.getKey(), configuration); - Object value = CloneBuilderImpl.isImmutable(e.getValue()) ? e.getValue() : - cloneObject(cloneOwner, field, e.getValue(), configuration); - if ((value instanceof Collection || value instanceof Map) && !(value instanceof IndirectCollection)) { + Object key = CloneBuilder.isImmutable(e.getKey()) ? e.getKey() : + cloneObject(cloneOwner, field, e.getKey(), configuration); + Object value = CloneBuilder.isImmutable(e.getValue()) ? e.getValue() : + cloneObject(cloneOwner, field, e.getValue(), configuration); + if ((value instanceof Collection || value instanceof Map) && !(value instanceof ChangeTrackingIndirectCollection)) { value = uow.createIndirectCollection(value, cloneOwner, field); } return Collections.singletonMap(key, value); @@ -130,8 +122,8 @@ private void cloneMapContent(Object cloneOwner, Field field, Map source, final Map m = (Map) target; final Entry tmp = source.entrySet().iterator().next(); // Note: If we encounter null -> null mapping first, the whole map will be treated as immutable type map, which can be incorrect - final boolean keyPrimitive = CloneBuilderImpl.isImmutable(tmp.getKey()); - final boolean valuePrimitive = CloneBuilderImpl.isImmutable(tmp.getValue()); + final boolean keyPrimitive = CloneBuilder.isImmutable(tmp.getKey()); + final boolean valuePrimitive = CloneBuilder.isImmutable(tmp.getValue()); for (Entry e : source.entrySet()) { Object key; Object value; @@ -155,7 +147,7 @@ private Object cloneObject(Object owner, Field field, Object obj, CloneConfigura if (obj == null) { clone = null; } else if (builder.isTypeManaged(obj.getClass())) { - clone = uow.registerExistingObject(obj, configuration.getDescriptor(), configuration.getPostRegister()); + clone = uow.registerExistingObject(obj, new CloneRegistrationDescriptor(configuration.getDescriptor()).postCloneHandlers(configuration.getPostRegister())); } else { clone = builder.buildClone(owner, field, obj, configuration.getDescriptor()); } @@ -168,15 +160,10 @@ void mergeChanges(Field field, Object target, Object originalValue, Object clone assert cloneValue instanceof Map; Map orig = (Map) originalValue; - Map clone = (Map) cloneValue; - if (clone instanceof IndirectCollection) { - clone = ((IndirectCollection>) clone).unwrap(); - } + final Map clone = cloneValue instanceof ChangeTrackingIndirectCollection ? + ((ChangeTrackingIndirectCollection>) cloneValue).unwrap() : (Map) cloneValue; if (orig == null) { - orig = (Map) createNewInstance(clone.getClass(), clone.size()); - if (orig == null) { - orig = createDefaultMap(clone.size()); - } + orig = (Map) createNewInstance(clone.getClass(), clone.size()).orElseGet(() -> createDefaultMap(clone.size())); EntityPropertiesUtils.setFieldValue(field, target, orig); } orig.clear(); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MergeManager.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MergeManager.java new file mode 100644 index 000000000..dcf1d2a78 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MergeManager.java @@ -0,0 +1,100 @@ +/* + * JOPA + * Copyright (C) 2023 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.sessions; + +import cz.cvut.kbss.jopa.sessions.change.Change; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; +import cz.cvut.kbss.jopa.sessions.change.UnitOfWorkChangeSet; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; +import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; + +import java.util.Objects; + +/** + * Merges changes that are made to clones to the registered original objects and live object cache. + */ +class MergeManager { + + private final AbstractUnitOfWork uow; + + private final CloneBuilder builder; + + MergeManager(AbstractUnitOfWork session, CloneBuilder cloneBuilder) { + this.uow = session; + this.builder = cloneBuilder; + } + + private void deleteObjectFromCache(Change changeSet) { + Object toDelete = changeSet.getOriginal(); + assert toDelete != null; + uow.removeObjectFromCache(toDelete, changeSet.getEntityContext()); + } + + /** + * Merge changes from one {@link Change}, which represents the changes made to clone, into the original object. + * + * @param changeSet ObjectChangeSet containing changes on a single object + */ + public void mergeChangesOnObject(ObjectChangeSet changeSet) { + Objects.requireNonNull(changeSet); + final Object clone = changeSet.getClone(); + assert clone != null && changeSet.getOriginal() != null; + builder.mergeChanges(changeSet); + updateCache(changeSet); + } + + private void updateCache(ObjectChangeSet changeSet) { + final Object changedObject = changeSet.getOriginal(); + final Object identifier = EntityPropertiesUtils.getIdentifier(changedObject, uow.getMetamodel()); + boolean preventCaching = changeSet.getChanges().stream().anyMatch(ChangeRecord::doesPreventCaching); + if (preventCaching) { + uow.removeObjectFromCache(changedObject, changeSet.getEntityContext()); + } else { + uow.putObjectIntoCache(identifier, changedObject, changeSet.getDescriptor()); + } + } + + /** + * Merge changes from the provided {@link UnitOfWorkChangeSet}. + * + * @param changeSet Change set from a single Unit of Work + */ + public void mergeChangesFromChangeSet(UnitOfWorkChangeSet changeSet) { + Objects.requireNonNull(changeSet); + changeSet.getNewObjects().forEach(this::mergeNewObject); + changeSet.getDeletedObjects().forEach(this::deleteObjectFromCache); + for (ObjectChangeSet objectChangeSet : changeSet.getExistingObjectsChanges()) { + mergeChangesOnObject(objectChangeSet); + } + } + + /** + * Merge a newly created object represented by an {@link Change} into the shared live object cache. + * + * @param changeSet ObjectChangeSet representing the new object + */ + public void mergeNewObject(Change changeSet) { + Objects.requireNonNull(changeSet); + final Object toCache = builder.buildClone(changeSet.getClone(), CloneConfiguration.withDescriptor(changeSet.getDescriptor())); + final Object identifier = EntityPropertiesUtils.getIdentifier(toCache, uow.getMetamodel()); + uow.registerOriginalForNewClone(changeSet.getClone(), toCache); + // Put the original object into the live object cache + uow.putObjectIntoCache(identifier, toCache, changeSet.getDescriptor()); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MergeManagerImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MergeManagerImpl.java deleted file mode 100644 index b9163a7d3..000000000 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MergeManagerImpl.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions; - -import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; -import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; - -import java.util.Objects; - -public class MergeManagerImpl implements MergeManager { - - private final UnitOfWorkImpl uow; - - private final CloneBuilder builder; - - MergeManagerImpl(UnitOfWorkImpl session) { - this.uow = session; - this.builder = session.getCloneBuilder(); - } - - private void deleteObjectFromCache(ObjectChangeSet changeSet) { - Object toDelete = changeSet.getChangedObject(); - assert toDelete != null; - uow.removeObjectFromCache(toDelete, changeSet.getEntityContext()); - } - - @Override - public Object mergeChangesOnObject(ObjectChangeSet changeSet) { - Objects.requireNonNull(changeSet); - final Object clone = changeSet.getCloneObject(); - if (clone == null) { - return null; - } - if (changeSet.getChangedObject() == null) { - // If the original is null, then we may have a new object - // but this should not happen since new objects are handled separately - if (uow.isObjectNew(clone)) { - mergeNewObject(changeSet); - } else { - throw new OWLPersistenceException("Cannot find the original object."); - } - } else { - builder.mergeChanges(changeSet); - updateCache(changeSet); - } - return clone; - } - - private void updateCache(ObjectChangeSet changeSet) { - final Object changedObject = changeSet.getChangedObject(); - final Object identifier = EntityPropertiesUtils.getIdentifier(changedObject, uow.getMetamodel()); - if (changeSet.isNew()) { - uow.putObjectIntoCache(identifier, changedObject, changeSet.getEntityDescriptor()); - } else { - boolean preventCaching = changeSet.getChanges().stream().anyMatch(ChangeRecord::doesPreventCaching); - if (preventCaching) { - uow.removeObjectFromCache(changedObject, changeSet.getEntityContext()); - } else { - uow.putObjectIntoCache(identifier, changedObject, changeSet.getEntityDescriptor()); - } - } - } - - @Override - public void mergeChangesFromChangeSet(UnitOfWorkChangeSet changeSet) { - Objects.requireNonNull(changeSet); - for (ObjectChangeSet objectChangeSet : changeSet.getExistingObjectsChanges()) { - mergeChangesOnObject(objectChangeSet); - } - changeSet.getNewObjects().forEach(this::mergeNewObject); - changeSet.getDeletedObjects().forEach(this::deleteObjectFromCache); - - } - - @Override - public void mergeNewObject(ObjectChangeSet changeSet) { - Objects.requireNonNull(changeSet); - if (!changeSet.isNew()) { - mergeChangesOnObject(changeSet); - return; - } - // Put the original object into the shared session cache - updateCache(changeSet); - } -} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MetamodelProvider.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MetamodelProvider.java index c87cc9dee..bf881e19c 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MetamodelProvider.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MetamodelProvider.java @@ -17,7 +17,9 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.model.metamodel.Metamodel; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.query.NamedQueryManager; +import cz.cvut.kbss.jopa.query.ResultSetMappingManager; public interface MetamodelProvider { @@ -26,13 +28,31 @@ public interface MetamodelProvider { * * @return Metamodel */ - Metamodel getMetamodel(); + MetamodelImpl getMetamodel(); /** - * Checks whether the specified class is a managed (=entity) type. + * Checks whether the specified class is an entity type. * * @param cls The class to check * @return Whether type is managed */ boolean isEntityType(Class cls); + + /** + * Gets {@link NamedQueryManager} for this persistence unit. + * + * @return {@code NamedQueryManager} + */ + default NamedQueryManager getNamedQueryManager() { + return getMetamodel().getNamedQueryManager(); + } + + /** + * Gets the SPARQL result set mapping manager ({@link ResultSetMappingManager}) for this persistence unit. + * + * @return {@code ResultSetMappingManager} + */ + default ResultSetMappingManager getResultSetMappingManager() { + return getMetamodel().getResultSetMappingManager(); + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MultilingualStringInstanceBuilder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MultilingualStringInstanceBuilder.java index ea34a0d48..daf461b05 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MultilingualStringInstanceBuilder.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/MultilingualStringInstanceBuilder.java @@ -17,15 +17,16 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectMultilingualString; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectMultilingualString; import cz.cvut.kbss.jopa.model.MultilingualString; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import java.lang.reflect.Field; class MultilingualStringInstanceBuilder extends AbstractInstanceBuilder { - MultilingualStringInstanceBuilder(CloneBuilderImpl builder, UnitOfWorkImpl uow) { + MultilingualStringInstanceBuilder(CloneBuilder builder, UnitOfWork uow) { super(builder, uow); } @@ -36,17 +37,17 @@ Object buildClone(Object cloneOwner, Field field, Object original, CloneConfigur } assert original instanceof MultilingualString; MultilingualString orig = (MultilingualString) original; - if (orig instanceof IndirectMultilingualString) { - orig = ((IndirectMultilingualString) orig).unwrap(); + if (orig instanceof ChangeTrackingIndirectMultilingualString) { + orig = ((ChangeTrackingIndirectMultilingualString) orig).unwrap(); } - return new IndirectMultilingualString(cloneOwner, field, uow, new MultilingualString(orig.getValue())); + return new ChangeTrackingIndirectMultilingualString(cloneOwner, field, uow, new MultilingualString(orig.getValue())); } @Override void mergeChanges(Field field, Object target, Object originalValue, Object cloneValue) { MultilingualString clone = (MultilingualString) cloneValue; - if (clone instanceof IndirectMultilingualString) { - clone = ((IndirectMultilingualString) clone).unwrap(); + if (clone instanceof ChangeTrackingIndirectMultilingualString) { + clone = ((ChangeTrackingIndirectMultilingualString) clone).unwrap(); } EntityPropertiesUtils .setFieldValue(field, target, clone != null ? new MultilingualString(clone.getValue()) : null); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWork.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWork.java new file mode 100644 index 000000000..ca7fd9f9b --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWork.java @@ -0,0 +1,151 @@ +package cz.cvut.kbss.jopa.sessions; + +import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException; +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent; +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.change.ChangeSetFactory; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; +import cz.cvut.kbss.jopa.sessions.validator.AttributeModificationValidator; +import cz.cvut.kbss.jopa.utils.Configuration; +import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; + +import java.lang.reflect.Field; +import java.net.URI; + +public class OnCommitChangePropagatingUnitOfWork extends AbstractUnitOfWork { + + OnCommitChangePropagatingUnitOfWork(AbstractSession parent, Configuration configuration) { + super(parent, configuration); + } + + @Override + void detachAllManagedInstances() { + cloneMapping.forEach(this::removeLazyLoadingProxies); + } + + private void removeLazyLoadingProxies(Object entity) { + assert entity != null; + final EntityType et = entityType(entity.getClass()); + for (FieldSpecification fs : et.getFieldSpecifications()) { + final Object value = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), entity); + if (value instanceof LazyLoadingProxy lazyLoadingProxy) { + EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, lazyLoadingProxy.unwrap()); + } + } + } + + @Override + void commitToStorage() { + calculateChanges(); + if (this.hasNew) { + persistNewObjects(); + } + uowChangeSet.getExistingObjectsChanges().forEach(chSet -> { + final IdentifiableEntityType et = entityType(chSet.getObjectClass()); + final Object entity = chSet.getClone(); + et.getLifecycleListenerManager().invokePreUpdateCallbacks(entity); + if (et.getLifecycleListenerManager().hasLifecycleCallback(LifecycleEvent.PRE_UPDATE)) { + // Recalculate changes if a preUpdate callback was called as it may have altered the entity state + changeCalculator.calculateChanges(chSet); + } + chSet.getChanges() + .forEach(record -> { + AttributeModificationValidator.verifyCanModify(record.getAttribute()); + storage.merge(entity, (FieldSpecification) record.getAttribute(), chSet.getDescriptor()); + }); + et.getLifecycleListenerManager().invokePostUpdateCallbacks(entity); + }); + uowChangeSet.getDeletedObjects().forEach(chSet -> { + final IdentifiableEntityType et = entityType(chSet.getObjectClass()); + final Object identifier = getIdentifier(chSet.getClone()); + storage.remove(identifier, chSet.getObjectClass(), chSet.getDescriptor()); + et.getLifecycleListenerManager().invokePostRemoveCallbacks(chSet.getClone()); + }); + validateIntegrityConstraints(); + storage.commit(); + } + + @Override + void calculateChanges() { + super.calculateChanges(); + cloneToOriginals.entrySet().stream().filter(e -> !deletedObjects.containsKey(e.getKey())).forEach(e -> { + final Object original = e.getValue(); + final Object clone = e.getKey(); + ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(original, clone, getDescriptor(clone)); + changeCalculator.calculateChanges(chSet); + processInferredValueChanges(chSet); + if (chSet.hasChanges()) { + uowChangeSet.addObjectChangeSet(chSet); + } + }); + if (uowChangeSet.hasChanges()) { + setHasChanges(); + } + } + + @Override + T mergeDetachedInternal(T toMerge, Descriptor descriptor) { + assert toMerge != null; + final IdentifiableEntityType et = (IdentifiableEntityType) entityType(toMerge.getClass()); + final URI idUri = EntityPropertiesUtils.getIdentifier(toMerge, et); + + final T clone = getInstanceForMerge(idUri, et, descriptor); + try { + ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(clone, toMerge, descriptor); + changeCalculator.calculateChanges(chSet); + chSet = processInferredValueChanges(chSet); + if (chSet.hasChanges()) { + final DetachedInstanceMerger merger = new DetachedInstanceMerger(this); + merger.mergeChangesFromDetachedToManagedInstance(chSet, descriptor); + uowChangeSet.addObjectChangeSet(copyChangeSet(chSet, getOriginal(clone), clone, descriptor)); + } + } catch (OWLEntityExistsException e) { + unregisterObject(clone); + throw e; + } + evictAfterMerge(et, idUri, descriptor); + setHasChanges(); + return et.getJavaType().cast(clone); + } + + @Override + public T getReference(Class cls, Object identifier, Descriptor descriptor) { + // TODO + return readObject(cls, identifier, descriptor); + } + + @Override + public void removeObject(Object entity) { + assert entity != null; + ensureManaged(entity); + + final Object identifier = getIdentifier(entity); + markCloneForDeletion(entity, identifier); + } + + @Override + public void unregisterObject(Object object) { + super.unregisterObject(object); + removeLazyLoadingProxies(object); + } + + @Override + public void attributeChanged(Object entity, Field f) { + // Do nothing + } + + @Override + public void attributeChanged(Object entity, FieldSpecification fieldSpec) { + // Do nothing + } + + @Override + public Object createIndirectCollection(Object collection, Object owner, Field field) { + // Do not create any special kind of collection, just return the argument + return collection; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/PrivilegedInstanceCreator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/PrivilegedInstanceCreator.java deleted file mode 100644 index 5cade6600..000000000 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/PrivilegedInstanceCreator.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions; - -import java.lang.reflect.Constructor; -import java.security.PrivilegedExceptionAction; - -/** - * Builds new instance with privileged access using the specified constructor. - */ -public class PrivilegedInstanceCreator implements PrivilegedExceptionAction { - - private final Constructor constructor; - private final Object[] params; - - PrivilegedInstanceCreator(Constructor constructor) { - this.constructor = constructor; - this.params = null; - } - - PrivilegedInstanceCreator(Constructor constructor, Object... params) { - this.constructor = constructor; - this.params = params; - } - - @Override - public Object run() throws Exception { - return constructor.newInstance(params); - } - -} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/RefreshInstanceMerger.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/RefreshInstanceMerger.java index f17b10fb0..411796b19 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/RefreshInstanceMerger.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/RefreshInstanceMerger.java @@ -17,8 +17,10 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectCollection; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectCollection; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; import cz.cvut.kbss.jopa.sessions.merge.DefaultValueMerger; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; @@ -44,15 +46,13 @@ class RefreshInstanceMerger { * @param changeSet Changes done */ void mergeChanges(ObjectChangeSet changeSet) { - final Object source = changeSet.getChangedObject(); - final Object target = changeSet.getCloneObject(); + final Object source = changeSet.getOriginal(); + final Object target = changeSet.getClone(); for (ChangeRecord change : changeSet.getChanges()) { final FieldSpecification att = change.getAttribute(); final Object sourceValue = EntityPropertiesUtils.getAttributeValue(att, source); - if (sourceValue instanceof IndirectCollection) { - final IndirectCollection col = (IndirectCollection) sourceValue; - final Object ic = indirectWrapperHelper - .createIndirectWrapper(col.unwrap(), target, att.getJavaField()); + if (sourceValue instanceof ChangeTrackingIndirectCollection col) { + final Object ic = indirectWrapperHelper.createIndirectWrapper(col.unwrap(), target, att.getJavaField()); merger.mergeValue(att, target, ic); } else { merger.mergeValue(att, target, sourceValue); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ServerSession.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ServerSession.java index a8f594bde..93ee43ab9 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ServerSession.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ServerSession.java @@ -20,19 +20,21 @@ import cz.cvut.kbss.jopa.accessors.DefaultStorageAccessor; import cz.cvut.kbss.jopa.accessors.StorageAccessor; import cz.cvut.kbss.jopa.model.AbstractEntityManager; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.metamodel.Metamodel; -import cz.cvut.kbss.jopa.query.NamedQueryManager; -import cz.cvut.kbss.jopa.query.ResultSetMappingManager; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; +import cz.cvut.kbss.jopa.query.criteria.CriteriaBuilderImpl; import cz.cvut.kbss.jopa.sessions.cache.CacheFactory; import cz.cvut.kbss.jopa.transactions.EntityTransaction; +import cz.cvut.kbss.jopa.utils.ChangeTrackingMode; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.jopa.utils.Wrapper; import cz.cvut.kbss.ontodriver.OntologyStorageProperties; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.net.URI; -import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @@ -44,22 +46,27 @@ */ public class ServerSession extends AbstractSession implements Wrapper { + private static final Logger LOG = LoggerFactory.getLogger(ServerSession.class); + private final MetamodelImpl metamodel; private CacheManager liveObjectCache; private StorageAccessor storageAccessor; + private final CriteriaBuilder criteriaBuilder; private Map runningTransactions; - ServerSession() { - super(new Configuration(Collections.emptyMap())); - this.metamodel = null; + ServerSession(MetamodelImpl metamodel) { + super(new Configuration()); + this.metamodel = metamodel; + this.criteriaBuilder = new CriteriaBuilderImpl(metamodel); } public ServerSession(OntologyStorageProperties storageProperties, Configuration configuration, MetamodelImpl metamodel) { super(configuration); this.metamodel = metamodel; + this.criteriaBuilder = new CriteriaBuilderImpl(metamodel); initialize(storageProperties, configuration, metamodel); } @@ -81,6 +88,25 @@ private void initialize(OntologyStorageProperties storageProperties, Configurati this.storageAccessor = new DefaultStorageAccessor(storageProperties, configuration.getProperties()); } + /** + * Acquires a {@link UnitOfWork} instance to perform transactional operations. + * + * @return UnitOfWork instance + */ + public UnitOfWork acquireUnitOfWork(Configuration configuration) { + final ChangeTrackingMode mode = ChangeTrackingMode.resolve(configuration); + return switch (mode) { + case IMMEDIATE -> { + LOG.trace("Acquiring change tracking UnitOfWork."); + yield new ChangeTrackingUnitOfWork(this, configuration); + } + case ON_COMMIT -> { + LOG.trace("Acquiring on commit change calculating UnitOfWork."); + yield new OnCommitChangePropagatingUnitOfWork(this, configuration); + } + }; + } + @Override protected ConnectionWrapper acquireConnection() { return new ConnectionWrapper(storageAccessor.acquireConnection()); @@ -122,11 +148,6 @@ public void close() { liveObjectCache.close(); } - @Override - public void removeObjectFromCache(Object object, URI context) { - // do nothing - } - @Override public MetamodelImpl getMetamodel() { return metamodel; @@ -138,13 +159,8 @@ public boolean isEntityType(Class cls) { } @Override - public NamedQueryManager getNamedQueryManager() { - return metamodel.getNamedQueryManager(); - } - - @Override - public ResultSetMappingManager getResultSetMappingManager() { - return metamodel.getResultSetMappingManager(); + public CriteriaBuilder getCriteriaBuilder() { + return criteriaBuilder; } @Override diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java similarity index 50% rename from jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java index 79eaeb786..bfab6fa77 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java @@ -18,22 +18,27 @@ package cz.cvut.kbss.jopa.sessions; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +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.CloneRegistrationDescriptor; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; +import cz.cvut.kbss.jopa.utils.Wrapper; import java.lang.reflect.Field; import java.net.URI; import java.util.List; -import java.util.function.Consumer; /** * Represents a persistence context. *

    * All interactions with objects managed in a persistence context are tracked by its corresponding UoW and on commit, - * the UoW propagates them into the changes into the storage. + * the UoW propagates them into the repository. */ -public interface UnitOfWork extends Session { +public interface UnitOfWork extends ConfigurationHolder, MetamodelProvider, Wrapper { /** * Clears this Unit of Work. @@ -41,7 +46,12 @@ public interface UnitOfWork extends Session { void clear(); /** - * Commit changes to the ontology. + * Notifies this Unit of Work that a transaction has begun. + */ + void begin(); + + /** + * Commit changes to the repository. */ void commit(); @@ -53,10 +63,9 @@ public interface UnitOfWork extends Session { void rollback(); /** - * Returns true if the specified entity is managed in the current persistence context. This method is used by the - * EntityManager's contains method. + * Checks whether the specified entity is managed in this Unit of Work. * - * @param entity Object + * @param entity Entity to check * @return {@literal true} if entity is managed, {@literal false} otherwise */ boolean contains(Object entity); @@ -69,27 +78,43 @@ public interface UnitOfWork extends Session { boolean isActive(); /** - * Returns true if this {@code UnitOfWork} represents persistence context of a currently running transaction. + * Returns true if this {@code UnitOfWork} represents the persistence context of a currently running transaction. * * @return True if in an active transaction */ boolean isInTransaction(); /** - * Return true if the given entity is managed. This means it is either in the shared session cache or it is a new - * object ready for persist. + * Returns true if this Unit of Work is currently committing changes to the repository. * - * @param entity Object - * @return boolean + * @return {@code true} if the UoW is in commit, {@code false} otherwise */ - boolean isObjectManaged(Object entity); + boolean isInCommit(); /** - * Checks whether context specified by {@code context} is consistent. + * Return true if the given entity is managed. *

    - * Can be {@code null}, indicating that consistency of the whole repository should be checked. + * This means it is tracked by this persistence context either as a new object or an existing object loaded from the + * repository. + * + * @param entity Object to check + * @return {@code true} when the entity is managed, {@code false} otherwise + */ + boolean isObjectManaged(Object entity); + + /** + * Checks whether the specified entity has been registered in this Unit of Work as a new object for persist. + * + * @param entity Object to check + * @return {@code true} when entity is managed and new, {@code false} otherwise + * @see #isObjectManaged(Object) + */ + boolean isObjectNew(Object entity); + + /** + * Checks whether the specified repository context is consistent. * - * @param context Context URI + * @param context Context URI, {@code null} indicates the whole repository should be checked * @return {@code true} if the context is consistent, {@code false} otherwise * @throws OWLPersistenceException If an ontology access error occurs */ @@ -100,13 +125,14 @@ public interface UnitOfWork extends Session { *

    * The value is set on the entity. * - * @param entity The entity to load field for - * @param field The field to load + * @param entity The entity to load field for + * @param fieldSpec Metamodel element representing the field to load + * @return The loaded field value * @throws NullPointerException If {@code entity} or {@code field} is {@code null} - * @throws OWLPersistenceException If an error occurs, this may be e. g. that the field is not present on the - * entity, an ontology access error occurred etc. + * @throws OWLPersistenceException If an error occurs, this may be e.g. that the field is not present on the entity, + * an ontology access error occurred etc. */ - void loadEntityField(T entity, Field field); + Object loadEntityField(T entity, FieldSpecification fieldSpec); /** * Merges the state of the given entity into the current persistence context. @@ -137,6 +163,19 @@ public interface UnitOfWork extends Session { */ T readObject(Class cls, Object identifier, Descriptor descriptor); + /** + * Reads an object but does not register it with this persistence context. + *

    + * Useful when the caller knows the object will be registered eventually by another routine. + * + * @param cls Expected result class + * @param identifier Object identifier + * @param descriptor Entity descriptor + * @return The retrieved object or {@code null} if there is no object with the specified identifier in the specified + * repository + */ + T readObjectWithoutRegistration(Class cls, Object identifier, Descriptor descriptor); + /** * Retrieves a reference to an object with the specified identifier. *

    @@ -154,27 +193,26 @@ public interface UnitOfWork extends Session { /** * Register an existing object in this Unit of Work. *

    - * This method creates a working clone of this object and puts the given object into this Unit of Work cache. + * This is a shortcut for {@link #registerExistingObject(Object, CloneRegistrationDescriptor)}. * * @param object Object * @param descriptor Entity descriptor identifying repository contexts - * @return Object Returns clone of the registered object + * @return Registered clone of the specified object + * @see #registerExistingObject(Object, CloneRegistrationDescriptor) */ Object registerExistingObject(Object object, Descriptor descriptor); /** - * Registers an existing object in this Unit of Work. + * Register an existing object in this Unit of Work. *

    - * Invokes the specified postClone procedures after the cloning takes place, passing the newly created clone as - * argument. + * Creates a working clone of the specified object according to the configuration and puts the given object into + * this Unit of Work cache. * - * @param object The object to register - * @param descriptor Descriptor identifying repository contexts - * @param postClone Handlers to be called after the original object is cloned on the clone - * @return Clone of the registered object - * @see #registerExistingObject(Object, Descriptor) + * @param object Object + * @param registrationDescriptor Configuration of the registration + * @return Registered clone of the specified object */ - Object registerExistingObject(Object object, Descriptor descriptor, List> postClone); + Object registerExistingObject(Object object, CloneRegistrationDescriptor registrationDescriptor); /** * Registers the specified new object in this Unit of Work. @@ -190,10 +228,9 @@ public interface UnitOfWork extends Session { void registerNewObject(Object object, Descriptor descriptor); /** - * Remove the given object. Calling this method causes the entity to be removed from the shared cache and a delete - * query is initiated on the ontology. + * Remove the given object from the repository. * - * @param object Object + * @param object Object to remove */ void removeObject(Object object); @@ -207,13 +244,33 @@ public interface UnitOfWork extends Session { void restoreRemovedObject(Object entity); /** - * Release the current unit of work. Calling this method disregards any changes made to clones. + * Puts the specified object into the live object cache. + * + * @param identifier Object identifier + * @param entity Object to cache + * @param descriptor Descriptor of repository context + */ + void putObjectIntoCache(Object identifier, Object entity, Descriptor descriptor); + + /** + * Removes the specified object from the live object cache. + *

    + * This is particularly meant for merging deleted objects from transactions. + * + * @param object Object to remove from cache + * @param context Entity context URI + */ + void removeObjectFromCache(Object object, URI context); + + /** + * Releases this unit of work. + *

    + * Releasing an active Unit of Work with uncommitted changes causes all pending changes to be discarded. */ - @Override void release(); /** - * Refreshes state of the object from the storage, overwriting any changes made to it. + * Refreshes the state of the specified object from the repository, overwriting any changes made to it. * * @param object The object to revert * @param Object type @@ -222,16 +279,22 @@ public interface UnitOfWork extends Session { void refreshObject(T object); /** - * This method returns true, if the UnitOfWork should be released after the commit call. This is done for inferred - * attributes, which cause the whole session cache to be invalidated. + * Finds clone of the specified original object. + * + * @param original The original object whose clone we are looking for + * @return The clone or null, if there is none + */ + Object getCloneForOriginal(Object original); + + /** + * Detaches the specified registered object from this Unit of Work. * - * @return True if the UnitOfWork should be released after commit. + * @param object Clone to detach */ - boolean shouldReleaseAfterCommit(); + void unregisterObject(Object object); /** - * Writes any uncommitted changes into the ontology. This method may be useful when flushing entity manager or - * closing sessions, because we don't want to let the changes to get lost. + * Writes any uncommitted changes into the ontology. */ void writeUncommittedChanges(); @@ -242,6 +305,13 @@ public interface UnitOfWork extends Session { */ List getContexts(); + /** + * Gets the registry of entity load state descriptors. + * + * @return {@code LoadStateDescriptorRegistry} for this persistence context + */ + LoadStateDescriptorRegistry getLoadStateRegistry(); + /** * Gets the load status of the specified attribute on the specified entity. * @@ -261,6 +331,27 @@ public interface UnitOfWork extends Session { */ LoadState isLoaded(Object entity); + /** + * Gets the lifecycle state of the specified entity. + *

    + * Note that since no repository is specified we can only determine if the entity is managed or removed. Therefore, + * if the case is different this method returns {@link EntityState#NOT_MANAGED}. + * + * @param entity Entity whose state to resolve + * @return Entity state + */ + EntityState getState(Object entity); + + /** + * Gets the lifecycle state of the specified entity with respect to a repository context indicated by the specified + * descriptor. + * + * @param entity Entity whose state to resolve + * @param descriptor Descriptor of repository contexts + * @return Entity state + */ + EntityState getState(Object entity, Descriptor descriptor); + /** * Checks whether the specified attribute value of the specified entity is inferred in the underlying repository. *

    @@ -274,5 +365,51 @@ public interface UnitOfWork extends Session { * @param value The value whose inference to examine * @return {@code true} if the entity attribute value is inferred, {@code false} otherwise */ - boolean isInferred(T entity, FieldSpecification attribute, Object value); + + + boolean isInferred(T entity, FieldSpecification attribute, Object value); + + /** + * Persists changed value of the specified field. + * + * @param entity Entity with changes (the clone) + * @param f The field whose value has changed + * @throws IllegalStateException If this UoW is not in transaction + * @see #attributeChanged(Object, FieldSpecification) + */ + void attributeChanged(Object entity, Field f); + + /** + * Persists changed value of the specified field. + * + * @param entity Entity with changes (the clone) + * @param fieldSpec Metamodel element representing the attribute that changed + * @throws IllegalStateException If this UoW is not in transaction + */ + void attributeChanged(Object entity, FieldSpecification fieldSpec); + + /** + * Creates an indirect collection that wraps the specified collection instance and propagates changes to this + * persistence context. + * + * @param collection Collection to be proxied + * @param owner Collection owner instance + * @param field Field filled with the collection + * @return Indirect collection + */ + Object createIndirectCollection(Object collection, Object owner, Field field); + + /** + * Gets a {@link SparqlQueryFactory} instance associated with this persistence context. + * + * @return SPARQL query factory + */ + SparqlQueryFactory sparqlQueryFactory(); + + /** + * Gets a {@link CriteriaBuilder} instance for building Criteria API queries. + * + * @return Criteria query builder + */ + CriteriaBuilder getCriteriaBuilder(); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/CacheFactory.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/CacheFactory.java index 363f737be..0d1f8332e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/CacheFactory.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/CacheFactory.java @@ -18,7 +18,6 @@ package cz.cvut.kbss.jopa.sessions.cache; import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; -import cz.cvut.kbss.jopa.sessions.CacheManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CacheManager.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/CacheManager.java similarity index 70% rename from jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CacheManager.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/CacheManager.java index 3b2c6de86..0c62694a3 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CacheManager.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/CacheManager.java @@ -15,9 +15,11 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.sessions; +package cz.cvut.kbss.jopa.sessions.cache; +import cz.cvut.kbss.jopa.model.Cache; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import java.util.Set; @@ -29,14 +31,14 @@ public interface CacheManager extends Cache { /** * Adds the specified object into the shared session cache. *

    - * If the cache already contains object with the specified identifier (and it is in the same repository context), - * it is replaced with the one passed as argument. + * If the cache already contains object with the specified identifier (and it is in the same repository context), it + * is replaced with the one passed as argument. * - * @param identifier Identifier of the specified object - * @param entity The object to be added into the cache - * @param descriptor Instance descriptor, contains info about repository context(s) and language tags + * @param identifier Identifier of the specified object + * @param entity The object to be added into the cache + * @param descriptors Instance descriptors */ - void add(Object identifier, Object entity, Descriptor descriptor); + void add(Object identifier, Object entity, Descriptors descriptors); /** * Gets entity with the specified identifier from the cache. @@ -46,14 +48,22 @@ public interface CacheManager extends Cache { * * @param cls Class of the entity * @param identifier Primary key of the entity - * @param descriptor Instance descriptor, contains info about repository context(s) and language tags + * @param descriptor Instance descriptor, contains info about repository context(s) and language tags * @return Entity with the specified primary key or {@code null} */ T get(Class cls, Object identifier, Descriptor descriptor); /** - * Removes objects with (possibly) inferred attributes from the cache. + * Gets {@link LoadStateDescriptor} that is associated with the specified cached instance. * + * @param instance Instance whose load state descriptor to retrieve + * @return Load state descriptor, {@code null} if this cache does not contain the specified instance + */ + LoadStateDescriptor getLoadStateDescriptor(Object instance); + + /** + * Removes objects with (possibly) inferred attributes from the cache. + *

    * This should be called when changes in the ontology may influence inference results. */ void evictInferredObjects(); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/Descriptors.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/Descriptors.java new file mode 100644 index 000000000..8ae805a92 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/Descriptors.java @@ -0,0 +1,13 @@ +package cz.cvut.kbss.jopa.sessions.cache; + +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; + +/** + * Descriptors of an entity. + * + * @param repositoryDescriptor Repository-related descriptor + * @param loadStateDescriptor Load state descriptor + */ +public record Descriptors(Descriptor repositoryDescriptor, LoadStateDescriptor loadStateDescriptor) { +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/DisabledCacheManager.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/DisabledCacheManager.java index 80bcb7124..bf494edb0 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/DisabledCacheManager.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/DisabledCacheManager.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.sessions.cache; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.sessions.CacheManager; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import java.net.URI; import java.util.Set; @@ -26,7 +26,7 @@ public class DisabledCacheManager implements CacheManager { @Override - public void add(Object identifier, Object entity, Descriptor descriptor) { + public void add(Object identifier, Object entity, Descriptors descriptors) { // Do nothing } @@ -35,6 +35,12 @@ public T get(Class cls, Object identifier, Descriptor descriptor) { return null; } + + @Override + public LoadStateDescriptor getLoadStateDescriptor(Object instance) { + return null; + } + @Override public void evictInferredObjects() { // Do nothing diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/EntityCache.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/EntityCache.java index 70f12daee..3028b4a65 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/EntityCache.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/EntityCache.java @@ -18,9 +18,14 @@ package cz.cvut.kbss.jopa.sessions.cache; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import java.net.URI; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; import java.util.function.Consumer; class EntityCache { @@ -30,7 +35,7 @@ class EntityCache { private static final String DEFAULT_CONTEXT_BASE = "http://defaultContext"; final Map, Object>>> repoCache; - final Map descriptors; + final Map descriptors; final URI defaultContext; EntityCache() { @@ -40,13 +45,13 @@ class EntityCache { this.defaultContext = URI.create(DEFAULT_CONTEXT_BASE + System.currentTimeMillis()); } - void put(Object identifier, Object entity, Descriptor descriptor) { + void put(Object identifier, Object entity, Descriptors descriptors) { assert identifier != null; assert entity != null; - assert isCacheable(descriptor); + assert isCacheable(descriptors.repositoryDescriptor()); final Class cls = entity.getClass(); - final URI ctx = descriptor.getSingleContext().orElse(defaultContext); + final URI ctx = descriptors.repositoryDescriptor().getSingleContext().orElse(defaultContext); Map, Object>> ctxMap; if (!repoCache.containsKey(ctx)) { @@ -63,10 +68,10 @@ void put(Object identifier, Object entity, Descriptor descriptor) { individualMap = ctxMap.get(identifier); } if (individualMap.containsKey(cls)) { - descriptors.remove(individualMap.get(cls)); + this.descriptors.remove(individualMap.get(cls)); } individualMap.put(cls, entity); - descriptors.put(entity, descriptor); + this.descriptors.put(entity, descriptors); } boolean isCacheable(Descriptor descriptor) { @@ -86,7 +91,7 @@ T getInternal(Class cls, Object identifier, Descriptor descriptor, Consum for (URI ctx : contexts) { final Map, Object> m = getMapForId(ctx, identifier); final Object result = m.get(cls); - if (result != null && descriptors.get(result).equals(descriptor)) { + if (result != null && descriptors.get(result).repositoryDescriptor().equals(descriptor)) { contextHandler.accept(ctx); return cls.cast(result); } @@ -94,6 +99,11 @@ T getInternal(Class cls, Object identifier, Descriptor descriptor, Consum return null; } + LoadStateDescriptor getLoadStateDescriptor(T instance) { + return descriptors.containsKey(instance) ? + (LoadStateDescriptor) descriptors.get(instance).loadStateDescriptor() : null; + } + boolean contains(Class cls, Object identifier, Descriptor descriptor) { assert cls != null; assert identifier != null; @@ -109,7 +119,7 @@ boolean contains(Class cls, Object identifier, Descriptor descriptor) { final Object result = m.get(cls); assert descriptors.containsKey(result); - if (descriptors.get(result).equals(descriptor)) { + if (descriptors.get(result).repositoryDescriptor().equals(descriptor)) { return true; } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/LruCacheManager.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/LruCacheManager.java index 92f69fda2..3d7c1a723 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/LruCacheManager.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/LruCacheManager.java @@ -19,13 +19,16 @@ import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.sessions.CacheManager; -import cz.cvut.kbss.jopa.utils.ErrorUtils; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; -import java.util.*; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -87,27 +90,40 @@ int getCapacity() { } @Override - public void add(Object primaryKey, Object entity, Descriptor descriptor) { - Objects.requireNonNull(primaryKey, ErrorUtils.getNPXMessageSupplier("primaryKey")); - Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity")); - Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor")); + public void add(Object identifier, Object entity, Descriptors descriptors) { + Objects.requireNonNull(identifier); + Objects.requireNonNull(entity); + Objects.requireNonNull(descriptors); writeLock.lock(); try { - entityCache.put(primaryKey, entity, descriptor); + entityCache.put(identifier, entity, descriptors); } finally { writeLock.unlock(); } } @Override - public T get(Class cls, Object primaryKey, Descriptor descriptor) { - if (cls == null || primaryKey == null || descriptor == null) { + public T get(Class cls, Object identifier, Descriptor descriptor) { + if (cls == null || identifier == null || descriptor == null) { + return null; + } + readLock.lock(); + try { + return entityCache.get(cls, identifier, descriptor); + } finally { + readLock.unlock(); + } + } + + @Override + public LoadStateDescriptor getLoadStateDescriptor(Object instance) { + if (instance == null) { return null; } readLock.lock(); try { - return entityCache.get(cls, primaryKey, descriptor); + return entityCache.getLoadStateDescriptor(instance); } finally { readLock.unlock(); } @@ -155,8 +171,8 @@ public boolean contains(Class cls, Object identifier, Descriptor descriptor) @Override public void evict(Class cls, Object identifier, URI context) { - Objects.requireNonNull(cls, ErrorUtils.getNPXMessageSupplier("cls")); - Objects.requireNonNull(identifier, ErrorUtils.getNPXMessageSupplier("primaryKey")); + Objects.requireNonNull(cls); + Objects.requireNonNull(identifier); writeLock.lock(); try { @@ -214,12 +230,12 @@ public void accept(LruCache.CacheNode cacheNode) { } @Override - void put(Object identifier, Object entity, Descriptor descriptor) { - if (!isCacheable(descriptor)) { + void put(Object identifier, Object entity, Descriptors descriptors) { + if (!isCacheable(descriptors.repositoryDescriptor())) { return; } - final URI ctx = descriptor.getSingleContext().orElse(defaultContext); - super.put(identifier, entity, descriptor); + final URI ctx = descriptors.repositoryDescriptor().getSingleContext().orElse(defaultContext); + super.put(identifier, entity, descriptors); cache.put(new LruCache.CacheNode(ctx, entity.getClass(), identifier), NULL_VALUE); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/TtlCacheManager.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/TtlCacheManager.java index 18d8dc31d..0975366a1 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/TtlCacheManager.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/cache/TtlCacheManager.java @@ -19,14 +19,19 @@ import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.sessions.CacheManager; -import cz.cvut.kbss.jopa.utils.ErrorUtils; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; @@ -126,19 +131,32 @@ public void close() { } @Override - public void add(Object primaryKey, Object entity, Descriptor descriptor) { - Objects.requireNonNull(primaryKey, ErrorUtils.getNPXMessageSupplier("primaryKey")); - Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity")); - Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor")); + public void add(Object identifier, Object entity, Descriptors descriptors) { + Objects.requireNonNull(identifier); + Objects.requireNonNull(entity); + Objects.requireNonNull(descriptors); acquireWriteLock(); try { - cache.put(primaryKey, entity, descriptor); + cache.put(identifier, entity, descriptors); } finally { releaseWriteLock(); } } + @Override + public LoadStateDescriptor getLoadStateDescriptor(Object instance) { + if (instance == null) { + return null; + } + readLock.lock(); + try { + return cache.getLoadStateDescriptor(instance); + } finally { + readLock.unlock(); + } + } + /** * Releases the live object cache. */ @@ -162,13 +180,13 @@ public void evictInferredObjects() { } @Override - public T get(Class cls, Object primaryKey, Descriptor descriptor) { - if (cls == null || primaryKey == null || descriptor == null) { + public T get(Class cls, Object identifier, Descriptor descriptor) { + if (cls == null || identifier == null || descriptor == null) { return null; } acquireReadLock(); try { - return cache.get(cls, primaryKey, descriptor); + return cache.get(cls, identifier, descriptor); } finally { releaseReadLock(); } @@ -177,7 +195,7 @@ public T get(Class cls, Object primaryKey, Descriptor descriptor) { /** * Get the set of inferred classes. *

    - * Inferred classes (i. e. classes with inferred attributes) are tracked separately since they require special + * Inferred classes (i.e. classes with inferred attributes) are tracked separately since they require special * behavior. * * @return Set of inferred classes @@ -202,13 +220,13 @@ public void setInferredClasses(Set> inferredClasses) { } @Override - public boolean contains(Class cls, Object primaryKey, Descriptor descriptor) { - if (cls == null || primaryKey == null || descriptor == null) { + public boolean contains(Class cls, Object identifier, Descriptor descriptor) { + if (cls == null || identifier == null || descriptor == null) { return false; } acquireReadLock(); try { - return cache.contains(cls, primaryKey, descriptor); + return cache.contains(cls, identifier, descriptor); } finally { releaseReadLock(); } @@ -228,8 +246,8 @@ public void evict(Class cls) { @Override public void evict(Class cls, Object identifier, URI context) { - Objects.requireNonNull(cls, ErrorUtils.getNPXMessageSupplier("cls")); - Objects.requireNonNull(identifier, ErrorUtils.getNPXMessageSupplier("primaryKey")); + Objects.requireNonNull(cls); + Objects.requireNonNull(identifier); acquireWriteLock(); try { @@ -309,12 +327,12 @@ private static final class TtlCache extends EntityCache { private final Map ttl = new HashMap<>(); @Override - void put(Object identifier, Object entity, Descriptor descriptor) { - if (!isCacheable(descriptor)) { + void put(Object identifier, Object entity, Descriptors descriptors) { + if (!isCacheable(descriptors.repositoryDescriptor())) { return; } - super.put(identifier, entity, descriptor); - final URI ctx = descriptor.getSingleContext().orElse(defaultContext); + super.put(identifier, entity, descriptors); + final URI ctx = descriptors.repositoryDescriptor().getSingleContext().orElse(defaultContext); updateTimeToLive(ctx); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/Change.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/Change.java new file mode 100644 index 000000000..e66b80cc8 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/Change.java @@ -0,0 +1,65 @@ +/* + * JOPA + * Copyright (C) 2023 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.sessions.change; + +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; + +import java.net.URI; + +/** + * Represents a change to an object made during a transaction. + */ +public interface Change { + + /** + * Gets the type of the changed object. + * + * @return Object type + */ + Class getObjectClass(); + + /** + * Gets the clone with changes. + * + * @return Clone + */ + Object getClone(); + + /** + * Gets the original object. + * + * @return Original + */ + Object getOriginal(); + + /** + * Gets descriptor of the changed object. + * + * @return Instance descriptor + */ + Descriptor getDescriptor(); + + /** + * Gets identifier of the repository context to which the changed object belongs. + * + * @return context URI + */ + default URI getEntityContext() { + return getDescriptor().getSingleContext().orElse(null); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeManagerImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeCalculator.java similarity index 60% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeManagerImpl.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeCalculator.java index 8956782d3..eb04dcf74 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeManagerImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeCalculator.java @@ -18,9 +18,10 @@ package cz.cvut.kbss.jopa.sessions.change; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.ChangeManager; +import cz.cvut.kbss.jopa.model.metamodel.Identifier; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; +import cz.cvut.kbss.jopa.proxy.lazy.gen.LazyLoadingEntityProxy; import cz.cvut.kbss.jopa.sessions.MetamodelProvider; -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,22 +32,34 @@ import java.util.Objects; import java.util.Set; -public class ChangeManagerImpl implements ChangeManager { +/** + * Calculates changes made on objects by comparing a clone with its registered original. + */ +public class ChangeCalculator { - private static final Logger LOG = LoggerFactory.getLogger(ChangeManagerImpl.class); + private static final Logger LOG = LoggerFactory.getLogger(ChangeCalculator.class); private final Map visitedObjects; private final MetamodelProvider metamodelProvider; private final ChangeDetector changeDetector; - public ChangeManagerImpl(MetamodelProvider metamodelProvider) { + public ChangeCalculator(MetamodelProvider metamodelProvider) { this.metamodelProvider = metamodelProvider; this.changeDetector = new ChangeDetectors(metamodelProvider); visitedObjects = new IdentityHashMap<>(); } - @Override + /** + * Checks whether there are any changes to the clone. + *

    + * It does an object value comparison, i.e. it compares each value of the clone against the original value and + * returns true if a change is found. + * + * @param original The original object. + * @param clone The clone, whose changes we are looking for. + * @return True if there is a change (at least one) or false, if the values are identical. + */ public boolean hasChanges(Object original, Object clone) { boolean res = hasChangesInternal(original, clone); visitedObjects.clear(); @@ -61,7 +74,7 @@ public boolean hasChanges(Object original, Object clone) { * @param clone The clone that may have changed. * @return True if the clone is in different state than the original. */ - boolean hasChangesInternal(Object original, Object clone) { + private boolean hasChangesInternal(Object original, Object clone) { if (clone == null && original == null) { return false; } @@ -73,6 +86,9 @@ boolean hasChangesInternal(Object original, Object clone) { } final Class cls = clone.getClass(); for (FieldSpecification fs : getFields(cls)) { + if (fs instanceof Identifier) { + continue; + } final Field f = fs.getJavaField(); final Object clVal = EntityPropertiesUtils.getFieldValue(f, clone); final Object origVal = EntityPropertiesUtils.getFieldValue(f, original); @@ -92,36 +108,53 @@ private boolean valueChanged(Object orig, Object clone) { return changeDetector.hasChanges(clone, orig); } - @Override + /** + * Calculates the changes that happened to the clone object. + *

    + * The changes are written into the {@link Change} passed in as argument. + * + * @param changeSet Contains references to the original and clone objects. Into this change set the changes should + * be propagated + * @return {@code true} if there were any changes, {@code false} otherwise + * @throws NullPointerException If {@code changeSet} is {@code null} + */ public boolean calculateChanges(ObjectChangeSet changeSet) { return calculateChangesInternal(Objects.requireNonNull(changeSet)); } /** - * This internal method does the actual changes calculation. It compares every non-static attribute of the clone to - * the original value. If the values are different, a change record is added into the change set. + * This internal method does the actual change calculation. + *

    + * It compares every persistent attribute of the clone to the original value. If the values are different, a change + * record is added to the change set. * - * @param changeSet The change set where change records will be put in. It also contains reference to the clone and - * original object. + * @param changeSet The change set where change records will be put in */ private boolean calculateChangesInternal(ObjectChangeSet changeSet) { LOG.trace("Calculating changes for change set {}.", changeSet); - Object original = changeSet.getChangedObject(); - Object clone = changeSet.getCloneObject(); - boolean changes = false; + Object original = changeSet.getOriginal(); + Object clone = changeSet.getClone(); + boolean changesFound = false; for (FieldSpecification fs : getFields(clone.getClass())) { - final Field f = fs.getJavaField(); - Object clVal = EntityPropertiesUtils.getFieldValue(f, clone); - Object origVal = EntityPropertiesUtils.getFieldValue(f, original); - if (clVal == null && origVal == null) { + if (fs instanceof Identifier) { + continue; + } + Object clVal = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), clone); + Object origVal = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), original); + if (shouldSkipLazyLoadedField(origVal, clVal)) { continue; } boolean changed = valueChanged(origVal, clVal); if (changed) { - changeSet.addChangeRecord(new ChangeRecordImpl(fs, clVal)); - changes = true; + changeSet.addChangeRecord(new ChangeRecord(fs, clVal)); + changesFound = true; } } - return changes; + return changesFound; + } + + private static boolean shouldSkipLazyLoadedField(Object originalValue, Object cloneValue) { + return (cloneValue instanceof LazyLoadingEntityProxy && originalValue == null) + || (cloneValue instanceof LazyLoadingProxy && !ChangeDetectors.isNonEmptyCollection(originalValue)); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeDetectors.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeDetectors.java index 04f45bceb..9e13f1d76 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeDetectors.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeDetectors.java @@ -28,26 +28,26 @@ class ChangeDetectors implements ChangeDetector { private final ChangeDetector mapChangeDetector; private final ChangeDetector collectionChangeDetector; - private final ChangeDetector managedTypeDector; + private final ChangeDetector managedTypeDetector; ChangeDetectors(MetamodelProvider metamodelProvider) { this.metamodelProvider = metamodelProvider; this.mapChangeDetector = new MapChangeDetector(this); this.collectionChangeDetector = new CollectionChangeDetector(this, metamodelProvider); - this.managedTypeDector = new ManagedTypeChangeDetector(metamodelProvider); + this.managedTypeDetector = new ManagedTypeChangeDetector(metamodelProvider); } @Override public boolean hasChanges(Object clone, Object original) { if ((clone == null && original != null) || (clone != null && original == null)) { - return true; + return isNonEmptyCollection(clone) && isNonEmptyCollection(original); } if (clone == null) { return false; } if (metamodelProvider.isEntityType(clone.getClass())) { - return managedTypeDector.hasChanges(clone, original); + return managedTypeDetector.hasChanges(clone, original); } else if (clone instanceof Collection) { return collectionChangeDetector.hasChanges(clone, original); } else if (clone instanceof Map) { @@ -55,4 +55,8 @@ public boolean hasChanges(Object clone, Object original) { } return !clone.equals(original); } + + static boolean isNonEmptyCollection(Object instance) { + return !(instance instanceof Collection) || !((Collection) instance).isEmpty(); + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeRecordImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeRecord.java similarity index 66% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeRecordImpl.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeRecord.java index c574db4be..37d0b7b13 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeRecordImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeRecord.java @@ -18,9 +18,11 @@ package cz.cvut.kbss.jopa.sessions.change; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; -public class ChangeRecordImpl implements ChangeRecord { +/** + * Record of a single change to an attribute. + */ +public class ChangeRecord { private final FieldSpecification attribute; @@ -28,40 +30,60 @@ public class ChangeRecordImpl implements ChangeRecord { private boolean preventsCaching; - public ChangeRecordImpl(FieldSpecification att, Object value) { + public ChangeRecord(FieldSpecification att, Object value) { assert att != null; this.attribute = att; this.newValue = value; } - @Override + /** + * Returns the new value of the attribute. + * + * @return Object + */ public Object getNewValue() { return newValue; } - @Override + /** + * Sets the new value of the attribute in case this change record needs to be updated. + * + * @param value The value to set + */ public void setNewValue(Object value) { this.newValue = value; } - @Override + /** + * Gets the attribute to which this change record is bound. + * + * @return the attribute + */ public FieldSpecification getAttribute() { return attribute; } - @Override + /** + * Marks this change record to prevent caching. + * + * @see #doesPreventCaching() + */ public void preventCaching() { this.preventsCaching = true; } - @Override + /** + * Whether this change record prevents caching of the instance on which the change is applied. + * + * @return Whether this change record prevents caching + */ public boolean doesPreventCaching() { return preventsCaching; } @Override public String toString() { - return "ChangeRecordImpl{" + + return "ChangeRecord{" + "attribute='" + attribute.getName() + '\'' + ", newValue=" + newValue + ", preventsCaching=" + preventsCaching + diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeSetFactory.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeSetFactory.java index 609b9e982..dcba4745c 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeSetFactory.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeSetFactory.java @@ -18,8 +18,6 @@ package cz.cvut.kbss.jopa.sessions.change; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkChangeSet; public class ChangeSetFactory { @@ -33,31 +31,41 @@ private ChangeSetFactory() { * @return New change set */ public static UnitOfWorkChangeSet createUoWChangeSet() { - return new UnitOfWorkChangeSetImpl(); + return new UnitOfWorkChangeSet(); } /** * Creates new change set for the specified original-clone pair. * - * @param original - * Original object - * @param clone - * Clone - * @param descriptor - * Entity descriptor + * @param original Original object + * @param clone Clone + * @param descriptor Entity descriptor * @return New object change set */ public static ObjectChangeSet createObjectChangeSet(Object original, Object clone, Descriptor descriptor) { - return new ObjectChangeSetImpl(original, clone, descriptor); + return new ObjectChangeSet(original, clone, descriptor); } /** - * Creates a new change set representing object removal. - * @param toDelete Object to remove + * Creates a change representing object deletion. + * + * @param clone Deleted object clone + * @param original Original of the deleted object * @param descriptor Entity descriptor - * @return New object change set + * @return Delete object change + */ + public static DeleteObjectChange createDeleteObjectChange(Object clone, Object original, Descriptor descriptor) { + return new DeleteObjectChange(clone, original, descriptor); + } + + /** + * Creates a change representing object persist. + * + * @param newObject Persisted object + * @param descriptor Entity descriptor + * @return New object change */ - public static ObjectChangeSet createDeleteObjectChangeSet(Object toDelete, Descriptor descriptor) { - return new DeleteObjectChangeSet(toDelete, descriptor); + public static NewObjectChange createNewObjectChange(Object newObject, Descriptor descriptor) { + return new NewObjectChange(newObject, descriptor); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/DeleteObjectChange.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/DeleteObjectChange.java new file mode 100644 index 000000000..27569db1c --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/DeleteObjectChange.java @@ -0,0 +1,61 @@ +/* + * JOPA + * Copyright (C) 2023 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.sessions.change; + +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; + +import java.util.Objects; + +/** + * Represents object deletion. + */ +public class DeleteObjectChange implements Change { + + private final Object clone; + + private final Object original; + + private final Descriptor descriptor; + + public DeleteObjectChange(Object clone, Object original, Descriptor descriptor) { + this.clone = Objects.requireNonNull(clone); + this.original = Objects.requireNonNull(original); + this.descriptor = Objects.requireNonNull(descriptor); + } + + @Override + public Class getObjectClass() { + return clone.getClass(); + } + + + @Override + public Object getClone() { + return clone; + } + + @Override + public Object getOriginal() { + return original; + } + + @Override + public Descriptor getDescriptor() { + return descriptor; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/DeleteObjectChangeSet.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/DeleteObjectChangeSet.java deleted file mode 100644 index 2c2881022..000000000 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/DeleteObjectChangeSet.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions.change; - -import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; - -import java.net.URI; -import java.util.Collections; -import java.util.Objects; -import java.util.Set; - -/** - * Change set representing object deletion. - *

    - * Supports only a subset of the {@link ObjectChangeSet} operations relevant for object removal during merge into parent - * session. - */ -public class DeleteObjectChangeSet implements ObjectChangeSet { - - private final Object object; - - private final Descriptor descriptor; - - public DeleteObjectChangeSet(Object object, Descriptor descriptor) { - this.object = Objects.requireNonNull(object); - this.descriptor = Objects.requireNonNull(descriptor); - } - - /** - * @throws UnsupportedOperationException This method is not supported - */ - @Override - public void addChangeRecord(ChangeRecord record) { - unsupported(); - } - - private static void unsupported() { - throw new UnsupportedOperationException("Can't invoke on " + DeleteObjectChangeSet.class.getSimpleName()); - } - - @Override - public Class getObjectClass() { - return object.getClass(); - } - - @Override - public Set getChanges() { - return Collections.emptySet(); - } - - @Override - public boolean hasChanges() { - return false; - } - - /** - * @throws UnsupportedOperationException This method is not supported - */ - @Override - public void setNew(boolean isNew) { - unsupported(); - } - - @Override - public boolean isNew() { - return false; - } - - @Override - public Object getCloneObject() { - return object; - } - - @Override - public Object getChangedObject() { - return object; - } - - @Override - public Descriptor getEntityDescriptor() { - return descriptor; - } - - @Override - public URI getEntityContext() { - return descriptor.getSingleContext().orElse(null); - } -} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/NewObjectChange.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/NewObjectChange.java new file mode 100644 index 000000000..03f27c6bc --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/NewObjectChange.java @@ -0,0 +1,40 @@ +package cz.cvut.kbss.jopa.sessions.change; + +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; + +import java.util.Objects; + +/** + * Represents addition of a new object. + */ +public class NewObjectChange implements Change { + + private final Object object; + + private final Descriptor descriptor; + + public NewObjectChange(Object object, Descriptor descriptor) { + this.object = Objects.requireNonNull(object); + this.descriptor = Objects.requireNonNull(descriptor); + } + + @Override + public Class getObjectClass() { + return object.getClass(); + } + + @Override + public Object getClone() { + return object; + } + + @Override + public Object getOriginal() { + return null; + } + + @Override + public Descriptor getDescriptor() { + return descriptor; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSetImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSet.java similarity index 72% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSetImpl.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSet.java index f8957a07d..cfbf0f8ab 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSetImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSet.java @@ -19,13 +19,10 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; -import java.net.URI; import java.util.*; -public class ObjectChangeSetImpl implements ObjectChangeSet { +public class ObjectChangeSet implements Change { // The object the changes are bound to private final Object changedObject; @@ -36,29 +33,40 @@ public class ObjectChangeSetImpl implements ObjectChangeSet { // A map of attributeName-ChangeRecord pairs to easily find the attributes to change private final Map, ChangeRecord> attributesToChange = new HashMap<>(); - // Does this change set represent a new object - private boolean isNew; - private final Descriptor descriptor; - public ObjectChangeSetImpl(Object changedObject, Object cloneObject, Descriptor descriptor) { + public ObjectChangeSet(Object changedObject, Object cloneObject, Descriptor descriptor) { this.changedObject = Objects.requireNonNull(changedObject); this.cloneObject = Objects.requireNonNull(cloneObject); this.descriptor = Objects.requireNonNull(descriptor); } - @Override + /** + * Adds a new change record to this change set. + *

    + * If there was a change for attribute represented by the new record, it will be overwritten. + * + * @param record The record to add + */ public void addChangeRecord(ChangeRecord record) { Objects.requireNonNull(record); attributesToChange.put(record.getAttribute(), record); } - @Override + /** + * Gets changes held in this change set. + * + * @return Set of changes + */ public Set getChanges() { return new HashSet<>(attributesToChange.values()); } - @Override + /** + * Whether this change set contains any changes. + * + * @return {@code true} if there are any changes in this change set, {@code false} otherwise + */ public boolean hasChanges() { return !attributesToChange.isEmpty(); } @@ -69,32 +77,17 @@ public Class getObjectClass() { } @Override - public Object getChangedObject() { + public Object getOriginal() { return changedObject; } @Override - public Object getCloneObject() { + public Object getClone() { return cloneObject; } @Override - public void setNew(boolean isNew) { - this.isNew = isNew; - } - - @Override - public boolean isNew() { - return isNew; - } - - @Override - public URI getEntityContext() { - return descriptor.getSingleContext().orElse(null); - } - - @Override - public Descriptor getEntityDescriptor() { + public Descriptor getDescriptor() { return descriptor; } } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkChangeSet.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/UnitOfWorkChangeSet.java similarity index 54% rename from jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkChangeSet.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/UnitOfWorkChangeSet.java index 709731a54..192d170a4 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkChangeSet.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/UnitOfWorkChangeSet.java @@ -15,34 +15,54 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.sessions; +package cz.cvut.kbss.jopa.sessions.change; -import java.util.Collection; -import java.util.Set; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; + +import java.util.*; + +/** + * A set of changes made in a {@link UnitOfWork}. + */ +public class UnitOfWorkChangeSet { + + private final Set deletedObjects; + private final Map objectChanges; + private final Set newObjectChanges; + + public UnitOfWorkChangeSet() { + this.objectChanges = new HashMap<>(); + this.deletedObjects = new HashSet<>(); + this.newObjectChanges = new HashSet<>(); + } -public interface UnitOfWorkChangeSet { /** * Add new ObjectChangeSet to this changeSet. * * @param objectChangeSet ObjectChangeSet */ - void addObjectChangeSet(ObjectChangeSet objectChangeSet); + public void addObjectChangeSet(ObjectChangeSet objectChangeSet) { + objectChanges.put(objectChangeSet.getOriginal(), objectChangeSet); + } /** - * Add a change set for newly created object. These changes are held in - * separate attribute and get special treatment when merged into shared - * session cache. + * Adds a change set for deleted object. * - * @param newObject ObjectChangeSet + * @param deletedObject The change set to add */ - void addNewObjectChangeSet(ObjectChangeSet newObject); + public void addDeletedObjectChangeSet(DeleteObjectChange deletedObject) { + deletedObjects.add(deletedObject); + } /** - * Adds a change set for deleted object. + * Add a change set for newly created object. These changes are held in separate attribute and get special treatment + * when merged into shared session cache. * - * @param deletedObject The change set to add + * @param newObject ObjectChangeSet */ - void addDeletedObjectChangeSet(ObjectChangeSet deletedObject); + public void addNewObjectChangeSet(NewObjectChange newObject) { + newObjectChanges.add(newObject); + } /** * Returns change sets for existing modified objects. @@ -51,14 +71,18 @@ public interface UnitOfWorkChangeSet { * * @return Collection of change sets */ - Collection getExistingObjectsChanges(); + public Collection getExistingObjectsChanges() { + return Collections.unmodifiableCollection(objectChanges.values()); + } /** * Removes change record of the specified original object, if present, cancelling the changes. * * @param original The object whose changes should be removed */ - void cancelObjectChanges(Object original); + public void cancelObjectChanges(Object original) { + objectChanges.remove(original); + } /** * Gets changes for the specified original object (if there are any). @@ -66,40 +90,52 @@ public interface UnitOfWorkChangeSet { * @param original The object for which changes should be found * @return Object change set or null, if the object has no changes */ - ObjectChangeSet getExistingObjectChanges(Object original); + public ObjectChangeSet getExistingObjectChanges(Object original) { + return objectChanges.get(original); + } /** * Returns the collection of deleted objects. * * @return Set of change sets */ - Set getDeletedObjects(); + public Set getDeletedObjects() { + return this.deletedObjects; + } /** * Returns the collection of change sets for newly created objects. * * @return Set of change sets */ - Set getNewObjects(); + public Set getNewObjects() { + return this.newObjectChanges; + } /** * Returns true if there are deleted objects in this change set. * * @return boolean */ - boolean hasDeleted(); + public boolean hasDeleted() { + return !deletedObjects.isEmpty(); + } /** * Returns true if this changeSet has any changes. * * @return boolean */ - boolean hasChanges(); + public boolean hasChanges() { + return hasDeleted() || hasNew() || !objectChanges.isEmpty(); + } /** * Are there any new objects in the change set? * * @return boolean */ - boolean hasNew(); + public boolean hasNew() { + return !this.newObjectChanges.isEmpty(); + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/UnitOfWorkChangeSetImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/UnitOfWorkChangeSetImpl.java deleted file mode 100644 index f50708a4d..000000000 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/UnitOfWorkChangeSetImpl.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.sessions.change; - -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkChangeSet; - -import java.util.*; - -public class UnitOfWorkChangeSetImpl implements UnitOfWorkChangeSet { - - private final Set deletedObjects; - private final Map objectChanges; - private final Set newObjectChanges; - - public UnitOfWorkChangeSetImpl() { - this.objectChanges = new HashMap<>(); - this.deletedObjects = new HashSet<>(); - this.newObjectChanges = new HashSet<>(); - } - - @Override - public void addObjectChangeSet(ObjectChangeSet objectChangeSet) { - if (objectChangeSet.isNew()) { - addNewObjectChangeSet(objectChangeSet); - } else { - objectChanges.put(objectChangeSet.getChangedObject(), objectChangeSet); - } - } - - @Override - public void addDeletedObjectChangeSet(ObjectChangeSet deletedObject) { - deletedObjects.add(deletedObject); - } - - @Override - public void addNewObjectChangeSet(ObjectChangeSet newObject) { - newObject.setNew(true); - newObjectChanges.add(newObject); - } - - @Override - public Collection getExistingObjectsChanges() { - return Collections.unmodifiableCollection(objectChanges.values()); - } - - @Override - public void cancelObjectChanges(Object original) { - objectChanges.remove(original); - } - - @Override - public ObjectChangeSet getExistingObjectChanges(Object original) { - return objectChanges.get(original); - } - - @Override - public Set getDeletedObjects() { - return this.deletedObjects; - } - - @Override - public Set getNewObjects() { - return this.newObjectChanges; - } - - @Override - public boolean hasDeleted() { - return !deletedObjects.isEmpty(); - } - - @Override - public boolean hasChanges() { - return hasDeleted() || hasNew() || !objectChanges.isEmpty(); - } - - @Override - public boolean hasNew() { - return !this.newObjectChanges.isEmpty(); - } -} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptor.java similarity index 86% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptor.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptor.java index cf0d93cfb..2983863cc 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptor.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptor.java @@ -27,28 +27,28 @@ import java.util.Objects; /** - * Describes an instance managed by a persistence context. + * Describes load state of an instance in a persistence context. */ -public class InstanceDescriptor { +public class LoadStateDescriptor { private final T instance; private final Map, LoadState> loadState; - InstanceDescriptor(T instance, EntityType et) { + public LoadStateDescriptor(T instance, EntityType et, LoadState defaultState) { this.instance = Objects.requireNonNull(instance); - this.loadState = mapInstanceAttributes(et); + this.loadState = mapInstanceAttributes(et, defaultState); } - InstanceDescriptor(T instance, InstanceDescriptor other) { + public LoadStateDescriptor(T instance, LoadStateDescriptor other) { this.instance = Objects.requireNonNull(instance); this.loadState = new HashMap<>(other.loadState); } - private Map, LoadState> mapInstanceAttributes(EntityType et) { + private Map, LoadState> mapInstanceAttributes(EntityType et, LoadState defaultState) { final Map, LoadState> map = new HashMap<>(); for (FieldSpecification fs : et.getFieldSpecifications()) { - map.put(fs, LoadState.NOT_LOADED); + map.put(fs, defaultState); } map.put(et.getIdentifier(), LoadState.LOADED); return map; @@ -87,7 +87,7 @@ public void setLoaded(FieldSpecification fs, LoadState state) { @Override public String toString() { - return "InstanceDescriptor{" + + return "LoadStateDescriptor{" + "instance=" + instance + ", loadState=" + loadState + '}'; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptorFactory.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactory.java similarity index 54% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptorFactory.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactory.java index 27f6d5228..c1407de4e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptorFactory.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactory.java @@ -20,14 +20,16 @@ import cz.cvut.kbss.jopa.model.LoadState; import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; /** - * Builds {@link InstanceDescriptor}s on various occasions. + * Builds {@link LoadStateDescriptor}s on various occasions. */ -public class InstanceDescriptorFactory { +public class LoadStateDescriptorFactory { - private InstanceDescriptorFactory() { + private LoadStateDescriptorFactory() { throw new AssertionError(); } @@ -39,8 +41,8 @@ private InstanceDescriptorFactory() { * @param Instance type * @return Fresh instance descriptor */ - public static InstanceDescriptor createNotLoaded(T instance, EntityType et) { - return new InstanceDescriptor<>(instance, et); + public static LoadStateDescriptor createNotLoaded(T instance, EntityType et) { + return new LoadStateDescriptor<>(instance, et, LoadState.NOT_LOADED); } /** @@ -51,18 +53,28 @@ public static InstanceDescriptor createNotLoaded(T instance, EntityType Instance type * @return Fresh instance descriptor with all loaded */ - public static InstanceDescriptor createAllLoaded(T instance, EntityType et) { - final InstanceDescriptor descriptor = createNotLoaded(instance, et); - et.getFieldSpecifications().forEach(fs -> descriptor.setLoaded(fs, LoadState.LOADED)); - return descriptor; + public static LoadStateDescriptor createAllLoaded(T instance, EntityType et) { + return new LoadStateDescriptor<>(instance, et, LoadState.LOADED); + } + + /** + * Creates an instance descriptor which marks all attributes except the identifier as having an unknown load state. + * + * @param instance Instance to create descriptor for + * @param et Entity type of the instance + * @param Instance type + * @return Fresh instance descriptor + */ + public static LoadStateDescriptor createAllUnknown(T instance, EntityType et) { + return new LoadStateDescriptor<>(instance, et, LoadState.UNKNOWN); } /** * Creates an instance descriptor which sets load status of attributes based on their value in the specified * instance as follows: *

    - * If the attribute value is not {@code null}, its status is set to {@link LoadState#LOADED}. If the value is {@code - * null} and the attribute fetch type is {@link FetchType#EAGER}, the status is also set to {@code LOADED}. + * If the attribute value is not {@code null}, its status is set to {@link LoadState#LOADED}. If the value is + * {@code null} and the attribute fetch type is {@link FetchType#EAGER}, the status is also set to {@code LOADED}. * Otherwise, the status is set to {@link LoadState#UNKNOWN}. * * @param instance Instance to create descriptor for @@ -70,16 +82,24 @@ public static InstanceDescriptor createAllLoaded(T instance, EntityType Instance type * @return Fresh instance descriptor */ - public static InstanceDescriptor create(T instance, EntityType et) { - final InstanceDescriptor descriptor = createNotLoaded(instance, et); + public static LoadStateDescriptor create(T instance, EntityType et) { + final LoadStateDescriptor descriptor = createNotLoaded(instance, et); et.getFieldSpecifications() - .forEach(fs -> descriptor.setLoaded(fs, fs.getFetchType() == FetchType.EAGER ? LoadState.LOADED : - EntityPropertiesUtils.getAttributeValue(fs, instance) != null ? - LoadState.LOADED : - LoadState.UNKNOWN)); + .forEach(fs -> descriptor.setLoaded(fs, getAttributeLoadState(instance, fs))); return descriptor; } + private static LoadState getAttributeLoadState(T instance, FieldSpecification fs) { + if (fs.getFetchType() == FetchType.EAGER) { + return LoadState.LOADED; + } + final Object attValue = EntityPropertiesUtils.getAttributeValue(fs, instance); + if (attValue instanceof LazyLoadingProxy) { + return LoadState.NOT_LOADED; + } + return attValue != null ? LoadState.LOADED : LoadState.UNKNOWN; + } + /** * Copies the load states from the specified original descriptor into a new descriptor for the specified instance. * @@ -88,7 +108,7 @@ public static InstanceDescriptor create(T instance, EntityType et) { * @param Instance type * @return Fresh instance descriptor with state copied from the specified one */ - public static InstanceDescriptor createCopy(T instance, InstanceDescriptor original) { - return new InstanceDescriptor<>(instance, original); + public static LoadStateDescriptor createCopy(T instance, LoadStateDescriptor original) { + return new LoadStateDescriptor<>(instance, original); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/CollectionValueMerger.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/CollectionValueMerger.java index 0b44f60c0..abe45aaae 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/CollectionValueMerger.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/CollectionValueMerger.java @@ -22,8 +22,8 @@ import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.PluralAttribute; import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import cz.cvut.kbss.jopa.utils.CollectionFactory; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.jopa.utils.MetamodelUtils; @@ -32,10 +32,10 @@ class CollectionValueMerger implements ValueMerger { - private final UnitOfWorkImpl uow; + private final UnitOfWork uow; private final ManagedTypeValueMerger managedTypeMerger; - CollectionValueMerger(UnitOfWorkImpl uow, ManagedTypeValueMerger managedTypeMerger) { + CollectionValueMerger(UnitOfWork uow, ManagedTypeValueMerger managedTypeMerger) { this.uow = uow; this.managedTypeMerger = managedTypeMerger; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/DefaultValueMerger.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/DefaultValueMerger.java index 07f54a4ef..f057d791b 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/DefaultValueMerger.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/DefaultValueMerger.java @@ -19,7 +19,7 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; public class DefaultValueMerger implements ValueMerger { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/DetachedValueMerger.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/DetachedValueMerger.java index 269a0dd0f..4048d4f30 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/DetachedValueMerger.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/DetachedValueMerger.java @@ -19,22 +19,22 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import java.util.Collection; import java.util.Map; public class DetachedValueMerger implements ValueMerger { - private final UnitOfWorkImpl uow; + private final UnitOfWork uow; private final ValueMerger defaultValueMerger; private final ManagedTypeValueMerger managedTypeMerger; private final CollectionValueMerger collectionMerger; private final MapValueMerger mapValueMerger; - public DetachedValueMerger(UnitOfWorkImpl uow) { + public DetachedValueMerger(UnitOfWork uow) { assert uow != null; this.uow = uow; this.defaultValueMerger = new DefaultValueMerger(); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/ManagedTypeValueMerger.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/ManagedTypeValueMerger.java index f5592600e..decd4bde9 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/ManagedTypeValueMerger.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/ManagedTypeValueMerger.java @@ -18,15 +18,15 @@ package cz.cvut.kbss.jopa.sessions.merge; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; class ManagedTypeValueMerger implements ValueMerger { - private final UnitOfWorkImpl uow; + private final UnitOfWork uow; - ManagedTypeValueMerger(UnitOfWorkImpl uow) { + ManagedTypeValueMerger(UnitOfWork uow) { this.uow = uow; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/MapValueMerger.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/MapValueMerger.java index b88f43172..442e8e6d5 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/MapValueMerger.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/MapValueMerger.java @@ -19,7 +19,7 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import cz.cvut.kbss.jopa.utils.CollectionFactory; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/ValueMerger.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/ValueMerger.java index 203e4664d..d70f2719f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/ValueMerger.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/merge/ValueMerger.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.jopa.sessions.merge; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; @FunctionalInterface public interface ValueMerger { diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CloneConfiguration.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/CloneConfiguration.java similarity index 59% rename from jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CloneConfiguration.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/CloneConfiguration.java index 888eb8e0e..56a8e66a2 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/sessions/CloneConfiguration.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/CloneConfiguration.java @@ -15,11 +15,12 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.sessions; +package cz.cvut.kbss.jopa.sessions.util; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -27,22 +28,23 @@ public class CloneConfiguration { - private final Descriptor descriptor; + private Descriptor descriptor; private final List> postRegister = new ArrayList<>(1); - public CloneConfiguration(Descriptor descriptor) { - this.descriptor = Objects.requireNonNull(descriptor); + private boolean forPersistenceContext; + + private CloneConfiguration() { } - public CloneConfiguration(Descriptor descriptor, List> handlers) { + public CloneConfiguration(Descriptor descriptor, boolean forPersistenceContext) { this.descriptor = Objects.requireNonNull(descriptor); - Objects.requireNonNull(handlers); - postRegister.addAll(handlers); + this.forPersistenceContext = forPersistenceContext; } - public void addPostRegisterHandler(Consumer handler) { - postRegister.add(handler); + public CloneConfiguration addPostRegisterHandlers(Collection> handlers) { + postRegister.addAll(handlers); + return this; } public Descriptor getDescriptor() { @@ -52,4 +54,19 @@ public Descriptor getDescriptor() { public List> getPostRegister() { return Collections.unmodifiableList(postRegister); } + + public CloneConfiguration forPersistenceContext(boolean value) { + this.forPersistenceContext = value; + return this; + } + + public boolean isForPersistenceContext() { + return forPersistenceContext; + } + + public static CloneConfiguration withDescriptor(Descriptor descriptor) { + final CloneConfiguration configuration = new CloneConfiguration(); + configuration.descriptor = descriptor; + return configuration; + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/CloneRegistrationDescriptor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/CloneRegistrationDescriptor.java new file mode 100644 index 000000000..5e66febe1 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/CloneRegistrationDescriptor.java @@ -0,0 +1,58 @@ +package cz.cvut.kbss.jopa.sessions.util; + +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +public class CloneRegistrationDescriptor { + + private final Descriptor descriptor; + + private final List> postCloneHandlers = new ArrayList<>(1); + + private boolean allEager; + + public CloneRegistrationDescriptor(Descriptor descriptor) { + this.descriptor = descriptor; + } + + public Descriptor getDescriptor() { + return descriptor; + } + + public CloneRegistrationDescriptor allEager(boolean value) { + this.allEager = value; + return this; + } + + public boolean isAllEager() { + return allEager; + } + + public CloneRegistrationDescriptor postCloneHandlers(Collection> handlers) { + postCloneHandlers.addAll(handlers); + return this; + } + + public List> getPostCloneHandlers() { + return postCloneHandlers; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CloneRegistrationDescriptor that)) return false; + return isAllEager() == that.isAllEager() && Objects.equals(getDescriptor(), + that.getDescriptor()) && Objects.equals( + getPostCloneHandlers(), that.getPostCloneHandlers()); + } + + @Override + public int hashCode() { + return Objects.hash(getDescriptor(), getPostCloneHandlers(), isAllEager()); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/LoadStateDescriptorRegistry.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/LoadStateDescriptorRegistry.java new file mode 100644 index 000000000..45402b203 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/LoadStateDescriptorRegistry.java @@ -0,0 +1,41 @@ +package cz.cvut.kbss.jopa.sessions.util; + +import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * Manages {@link cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor}s for a persistence context. + */ +public class LoadStateDescriptorRegistry { + + private final Map registry = new IdentityHashMap<>(); + + private final Function entityStringifier; + + public LoadStateDescriptorRegistry(Function entityStringifier) { + this.entityStringifier = entityStringifier; + } + + public void put(Object instance, LoadStateDescriptor descriptor) { + registry.put(instance, descriptor); + } + + public LoadStateDescriptor get(T instance) { + if (!contains(instance)) { + throw new OWLPersistenceException("Fatal error, LoadStateDescriptorRegistry is missing descriptor for " + entityStringifier.apply(instance)); + } + return registry.get(instance); + } + + public boolean contains(Object instance) { + return registry.containsKey(instance); + } + + public void clear() { + registry.clear(); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/LoadingParameters.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/LoadingParameters.java similarity index 93% rename from jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/LoadingParameters.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/LoadingParameters.java index 1ddd6c121..cbd2ff07f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/LoadingParameters.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/util/LoadingParameters.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.sessions; +package cz.cvut.kbss.jopa.sessions.util; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; @@ -50,7 +50,7 @@ private boolean paramsLoaded() { return cls != null && identifier != null && descriptor != null; } - public Class getEntityType() { + public Class getEntityClass() { return cls; } @@ -79,10 +79,9 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (!(o instanceof LoadingParameters)) { + if (!(o instanceof LoadingParameters that)) { return false; } - LoadingParameters that = (LoadingParameters) o; if (forceEager != that.forceEager) { return false; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/AttributeModificationValidator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/AttributeModificationValidator.java index 981b6d3fa..a5daa446a 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/AttributeModificationValidator.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/AttributeModificationValidator.java @@ -34,7 +34,7 @@ private AttributeModificationValidator() { /** * Checks whether the specified field can be modified. - * + *

    * Currently, only modification of lexical form attributes are rejected immediately. * * @param fieldSpec Specification of mapped field to verify diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/CardinalityConstraintsValidator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/CardinalityConstraintsValidator.java index a41acd79b..a70fdad32 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/CardinalityConstraintsValidator.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/CardinalityConstraintsValidator.java @@ -23,6 +23,7 @@ import cz.cvut.kbss.jopa.model.metamodel.Attribute; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.QueryAttribute; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; import java.lang.reflect.Field; import java.util.Collection; @@ -80,7 +81,7 @@ public void validate(Object identifier, FieldSpecification attribute, Obje } private static int extractValueCount(Object value) { - if (value == null) { + if (value == null || value instanceof LazyLoadingProxy) { return 0; } return value instanceof Collection ? ((Collection) value).size() : 1; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/IntegrityConstraintsValidator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/IntegrityConstraintsValidator.java index 6c869e980..e677d9d87 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/IntegrityConstraintsValidator.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/validator/IntegrityConstraintsValidator.java @@ -21,8 +21,8 @@ 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.sessions.ChangeRecord; -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import java.util.Objects; @@ -77,7 +77,7 @@ public void validate(ObjectChangeSet changeSet, Metamodel metamodel) { Objects.requireNonNull(metamodel); final EntityType et = metamodel.entity(changeSet.getObjectClass()); - final Object id = EntityPropertiesUtils.getIdentifier(changeSet.getCloneObject(), et); + final Object id = EntityPropertiesUtils.getIdentifier(changeSet.getClone(), et); for (ChangeRecord change : changeSet.getChanges()) { validate(id, change.getAttribute(), change.getNewValue()); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/EntityTransactionImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/EntityTransactionImpl.java index 10b937be9..bc1c5c321 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/EntityTransactionImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/EntityTransactionImpl.java @@ -42,9 +42,8 @@ public void begin() { if (isActive()) { throw new IllegalStateException("Transaction already active!"); } - wrapper.begin(); this.active = true; - wrapper.getEntityManager().transactionStarted(this); + wrapper.begin(); LOG.trace("EntityTransaction begin."); } @@ -55,18 +54,10 @@ public void commit() { LOG.trace("EntityTransaction commit started."); if (rollbackOnly) { throw new RollbackException("Trying to commit transaction marked as rollback only."); - } else { - try { - wrapper.getTransactionUOW().commit(); - } catch (RuntimeException ex) { - wrapper.getEntityManager().removeCurrentPersistenceContext(); - throw new RollbackException(ex); - } } + wrapper.commit(); } finally { - if (wrapper.getTransactionUOW().shouldReleaseAfterCommit()) { - wrapper.getEntityManager().removeCurrentPersistenceContext(); - } + wrapper.transactionFinished(); cleanup(); LOG.trace("EntityTransaction commit finished."); } @@ -81,15 +72,13 @@ private void verifyTransactionActive(String method) { private void cleanup() { this.active = false; this.rollbackOnly = false; - wrapper.setTransactionUOW(null); - wrapper.getEntityManager().transactionFinished(this); } @Override public void rollback() { verifyTransactionActive("rollback"); - wrapper.getTransactionUOW().rollback(); - wrapper.getEntityManager().removeCurrentPersistenceContext(); + wrapper.rollback(); + wrapper.transactionFinished(); cleanup(); LOG.trace("EntityTransaction rolled back."); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/EntityTransactionWrapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/EntityTransactionWrapper.java index d1fecd79b..d991a0e24 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/EntityTransactionWrapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/EntityTransactionWrapper.java @@ -17,17 +17,24 @@ */ package cz.cvut.kbss.jopa.transactions; +import cz.cvut.kbss.jopa.exceptions.RollbackException; import cz.cvut.kbss.jopa.model.AbstractEntityManager; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; -public class EntityTransactionWrapper extends TransactionWrapperImpl { +/** + * Wraps an {@link EntityTransaction} and mediates communication with the current persistence context and the {@link + * cz.cvut.kbss.jopa.model.EntityManager}. + */ +public class EntityTransactionWrapper { + private final AbstractEntityManager entityManager; private EntityTransaction entityTransaction; + private UnitOfWork transactionUOW; public EntityTransactionWrapper(AbstractEntityManager entityManger) { - super(entityManger); + this.entityManager = entityManger; } - @Override public EntityTransaction getTransaction() { if (entityTransaction == null) { entityTransaction = new EntityTransactionImpl(this); @@ -36,6 +43,26 @@ public EntityTransaction getTransaction() { } void begin() { - setTransactionUOW(getEntityManager().getCurrentPersistenceContext()); + this.transactionUOW = entityManager.getCurrentPersistenceContext(); + transactionUOW.begin(); + entityManager.transactionStarted(entityTransaction); + } + + void commit() { + try { + transactionUOW.commit(); + } catch (RuntimeException e) { + rollback(); + throw new RollbackException(e); + } + } + + void transactionFinished() { + entityManager.transactionFinished(entityTransaction); + this.transactionUOW = null; + } + + void rollback() { + entityManager.removeCurrentPersistenceContext(); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/TransactionWrapperImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/TransactionWrapperImpl.java deleted file mode 100644 index 6959e166c..000000000 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/transactions/TransactionWrapperImpl.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.transactions; - -import cz.cvut.kbss.jopa.model.AbstractEntityManager; -import cz.cvut.kbss.jopa.sessions.UnitOfWork; - -public abstract class TransactionWrapperImpl implements TransactionWrapper { - - private final AbstractEntityManager entityManager; - - private UnitOfWork transactionUOW; - - public TransactionWrapperImpl(AbstractEntityManager entityManger) { - this.entityManager = entityManger; - } - - public UnitOfWork getTransactionUOW() { - return transactionUOW; - } - - public void setTransactionUOW(UnitOfWork transactionUOW) { - this.transactionUOW = transactionUOW; - } - - AbstractEntityManager getEntityManager() { - return entityManager; - } -} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/ChangeTrackingMode.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/ChangeTrackingMode.java new file mode 100644 index 000000000..811d882fb --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/ChangeTrackingMode.java @@ -0,0 +1,45 @@ +package cz.cvut.kbss.jopa.utils; + +import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; + +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * Specifies how changes to managed objects are tracked in transactions. + */ +public enum ChangeTrackingMode { + + /** + * Tracks changes immediately as they are made and propagates them to a transaction snapshot of the repository + * managed by the underlying OntoDriver. + */ + IMMEDIATE, + /** + * Calculates changes on commit, propagating them immediately to the underlying repository. + */ + ON_COMMIT; + + /** + * Resolves change tracking mode from the specified configuration. + * + * @param configuration Configuration to resolve change tracking mode from + * @return Resolved {@code ChangeTrackingMode} + */ + public static ChangeTrackingMode resolve(Configuration configuration) { + Objects.requireNonNull(configuration); + final String configValue = configuration.get(JOPAPersistenceProperties.CHANGE_TRACKING_MODE); + final Optional result = Stream.of(values()) + .filter(m -> m.toString().equalsIgnoreCase(configValue)) + .findAny(); + return result.orElseGet(() -> { + // For RDF4J driver, no other mode makes sense as the driver does not support transactional snapshots + if ("cz.cvut.kbss.ontodriver.rdf4j.Rdf4jDataSource".equals(configuration.get(JOPAPersistenceProperties.DATA_SOURCE_CLASS))) { + return ON_COMMIT; + } + // Default legacy mode + return IMMEDIATE; + }); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/CollectionFactory.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/CollectionFactory.java index 5e766bad4..514a519ad 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/CollectionFactory.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/CollectionFactory.java @@ -53,15 +53,11 @@ public static Collection createInstance(Collection collection) { * @return Collection implementation instance */ public static Collection createDefaultCollection(CollectionType collectionType) { - switch (collectionType) { - case LIST: - return new ArrayList<>(); - case SET: - case COLLECTION: // Intentional fall-through - return new HashSet<>(); - default: - throw new IllegalArgumentException("Collection type " + collectionType + " is not supported."); - } + return switch (collectionType) { + case LIST -> new ArrayList<>(); + case SET, COLLECTION -> new HashSet<>(); + default -> throw new IllegalArgumentException("Collection type " + collectionType + " is not supported."); + }; } /** diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/utils/Configuration.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/Configuration.java similarity index 58% rename from jopa-api/src/main/java/cz/cvut/kbss/jopa/utils/Configuration.java rename to jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/Configuration.java index ba639589f..be168bbe6 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/utils/Configuration.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/Configuration.java @@ -40,28 +40,67 @@ public Configuration(Map properties) { this.properties = new HashMap<>(properties); } + /** + * Gets the configured value of the specified property. + * + * @param property Property whose value to get + * @return Configured value, {@code null} if property is not configured + */ public String get(String property) { return get(property, null); } + /** + * Gets the configured value of the specified property. + *

    + * The specified default value is returned if the property is not configured. + * + * @param property Property whose value to get + * @param defaultValue Default value to return if no value is configured + * @return Configured value, {@code defaultValue} if property is not configured + */ public String get(String property, String defaultValue) { return properties.getOrDefault(property, defaultValue); } + /** + * Gets the configured value of the specified boolean-valued property. + * + * @param property Property whose value to get + * @return {@code true} if the specified property is configured and its value is the boolean true, {@code false} + * otherwise + */ public boolean is(String property) { final String propertyValue = get(property, null); return Boolean.parseBoolean(propertyValue); } + /** + * Sets the specified value for the specified property. + * + * @param property Property whose value to set + * @param value Value to set + */ public synchronized void set(String property, String value) { Objects.requireNonNull(property); properties.put(property, value); } + /** + * Checks whether this configuration contains an explicitly set value of the specified property. + * + * @param property Property whose presence to check + * @return {@code true} if the specified property is configured, {@code false} otherwise + */ public boolean contains(String property) { return properties.containsKey(property); } + /** + * Gets an unmodifiable view of the underlying map of configuration. + * + * @return Unmodifiable Map + */ public Map getProperties() { return Collections.unmodifiableMap(properties); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/JOPALazyUtils.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/JOPALazyUtils.java new file mode 100644 index 000000000..2dd33d2d8 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/JOPALazyUtils.java @@ -0,0 +1,51 @@ +package cz.cvut.kbss.jopa.utils; + +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; +import cz.cvut.kbss.jopa.proxy.lazy.gen.LazyLoadingEntityProxy; + +import java.util.Objects; + +/** + * Utilities related to lazy loading. + */ +public class JOPALazyUtils { + + /** + * Triggers loading on the specified lazy loading proxy. + *

    + * If the specified object is not a {@link cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy} or is a proxy that is + * already loaded, nothing happens. + * + * @param proxy Proxy to load + */ + public static void load(Object proxy) { + if (proxy instanceof LazyLoadingProxy p) { + p.triggerLazyLoading(); + } + } + + /** + * Checks whether the specified object has been loaded or is still a lazy loading proxy. + * + * @param proxy Proxy to check + * @return {@code true} if the object is a {@code LazyLoadingProxy} and is not loaded, {@code false} otherwise + */ + public static boolean isLoaded(Object proxy) { + return !(proxy instanceof LazyLoadingProxy lazyLoadingProxy) || (lazyLoadingProxy.isLoaded()); + } + + /** + * Gets the class represented by the specified lazy loading proxy. + *

    + * If the specified object proxies an entity, the corresponding entity class is returned. If the object proxies a + * collection, the corresponding interface is returned. If the specified object is not a + * {@link cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy}, its runtime class is returned. + * + * @param proxy Proxy whose class to get + * @return Runtime class unwrapped from a lazy loading proxy + */ + public static Class getClass(Object proxy) { + Objects.requireNonNull(proxy); + return (proxy instanceof LazyLoadingEntityProxy entityProxy) ? entityProxy.getProxiedClass() : proxy.getClass(); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/MetamodelUtils.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/MetamodelUtils.java index 164e792d4..a357dbe6f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/MetamodelUtils.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/utils/MetamodelUtils.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.utils; import cz.cvut.kbss.jopa.model.metamodel.Metamodel; +import cz.cvut.kbss.jopa.model.metamodel.gen.GeneratedEntityClass; import java.net.URI; import java.util.Collection; @@ -53,4 +54,18 @@ public static void checkForModuleSignatureExtension(Collection types, Metamod } } } + + /** + * Gets an entity class corresponding to the specified class. + *

    + * This method returns either the provided class or its superclass in case when the provided class is a generated + * subclass created by JOPA. + * + * @param cls Class to process + * @param Type + * @return Entity class + */ + public static Class getEntityClass(Class cls) { + return cls.getAnnotation(GeneratedEntityClass.class) != null ? cls.getSuperclass() : cls; + } } diff --git a/jopa-impl/src/main/resources/META-INF/aop.xml b/jopa-impl/src/main/resources/META-INF/aop.xml deleted file mode 100644 index 79b542057..000000000 --- a/jopa-impl/src/main/resources/META-INF/aop.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/accessors/DefaultStorageAccessorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/accessors/DefaultStorageAccessorTest.java index 0ea401671..1170ddb15 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/accessors/DefaultStorageAccessorTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/accessors/DefaultStorageAccessorTest.java @@ -17,36 +17,35 @@ */ package cz.cvut.kbss.jopa.accessors; +import cz.cvut.kbss.jopa.exception.DataSourceCreationException; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; import cz.cvut.kbss.ontodriver.OntologyStorageProperties; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; import java.lang.reflect.Field; import java.net.URI; import java.util.Collections; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class DefaultStorageAccessorTest { private static final String DATA_SOURCE_CLASS = DataSourceStub.class.getName(); private static final String INVALID_DATA_SOURCE_CLASS = InvalidDataSource.class.getName(); - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } - @Test void createsDataSourceFromClassName() { final DefaultStorageAccessor a = new DefaultStorageAccessor(storageProperties(DATA_SOURCE_CLASS), Collections.emptyMap()); assertNotNull(a); assertTrue(a.isOpen()); - assertTrue(a.acquireConnection() instanceof DataSourceStub.ConnectionStub); + assertInstanceOf(DataSourceStub.ConnectionStub.class, a.acquireConnection()); } private OntologyStorageProperties storageProperties(String driverClass) { @@ -65,7 +64,7 @@ void throwsExceptionWhenDataSourceClassWithoutProperConstructorIsProvided() { void throwsExceptionWhenUnknownClassIsSpecifiedAsDataSource() { final String unknownClass = "cz.cvut.kbss.jopa.UnknownDataSource"; final DataSourceCreationException ex = assertThrows(DataSourceCreationException.class, - () -> new DefaultStorageAccessor(storageProperties(unknownClass), Collections.emptyMap())); + () -> new DefaultStorageAccessor(storageProperties(unknownClass), Collections.emptyMap())); assertEquals("Unable to find OntoDriver data source class " + unknownClass, ex.getMessage()); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/NoopInstantiableTypeGenerator.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/NoopInstantiableTypeGenerator.java new file mode 100644 index 000000000..e365cd625 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/NoopInstantiableTypeGenerator.java @@ -0,0 +1,19 @@ +package cz.cvut.kbss.jopa.environment; + +import cz.cvut.kbss.jopa.model.metamodel.gen.PersistenceContextAwareClassGenerator; + +/** + * Returns the specified entity class. + */ +public class NoopInstantiableTypeGenerator implements PersistenceContextAwareClassGenerator { + + public static final NoopInstantiableTypeGenerator INSTANCE = new NoopInstantiableTypeGenerator(); + + private NoopInstantiableTypeGenerator() { + } + + @Override + public Class generate(Class entityClass) { + return entityClass; + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassA.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassA.java index 1c7358626..d91b2fc58 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassA.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassA.java @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.environment; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.*; import cz.cvut.kbss.jopa.model.metamodel.PropertyInfo; @@ -31,7 +32,7 @@ @VariableResult(name = "y", type = String.class) }) @OWLClass(iri = Vocabulary.c_OwlClassA) -public class OWLClassA { +public class OWLClassA implements HasUri { public static final String VARIABLE_MAPPING = "OWLClassA.testMapping"; @@ -68,16 +69,12 @@ public OWLClassA(OWLClassA other) { this.types = new HashSet<>(other.types); } - /** - * @param uri the uri to set - */ + @Override public void setUri(URI uri) { this.uri = uri; } - /** - * @return the uri - */ + @Override public URI getUri() { return uri; } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassB.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassB.java index 551443a82..8d2800c4e 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassB.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassB.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Set; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.Id; import cz.cvut.kbss.jopa.model.annotations.OWLClass; @@ -29,7 +30,7 @@ import cz.cvut.kbss.jopa.model.annotations.Properties; @OWLClass(iri = Vocabulary.c_OwlClassB) -public class OWLClassB { +public class OWLClassB implements HasUri { private static final String STR_ATT_FIELD = "stringAttribute"; private static final String PROPERTIES_FIELD = "properties"; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassC.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassC.java index 8a2905388..1050bb96c 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassC.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassC.java @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.environment; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.*; import java.lang.reflect.Field; @@ -24,7 +25,7 @@ import java.util.List; @OWLClass(iri = Vocabulary.c_OwlClassC) -public class OWLClassC { +public class OWLClassC implements HasUri { private static final String REF_LIST_FIELD = "referencedList"; private static final String SIMPLE_LIST_FIELD = "simpleList"; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassD.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassD.java index fb6e1e85e..245fd43a7 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassD.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassD.java @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.environment; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.Id; import cz.cvut.kbss.jopa.model.annotations.OWLClass; @@ -27,7 +28,7 @@ import java.net.URI; @OWLClass(iri = Vocabulary.c_OwlClassD) -public class OWLClassD { +public class OWLClassD implements HasUri { private static final String CLS_A_FIELD = "owlClassA"; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassE.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassE.java index 97ae3fe15..902823cab 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassE.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassE.java @@ -20,12 +20,13 @@ import java.lang.reflect.Field; import java.net.URI; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.Id; import cz.cvut.kbss.jopa.model.annotations.OWLClass; import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; @OWLClass(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassE") -public class OWLClassE { +public class OWLClassE implements HasUri { private static final String STR_ATT_FIELD = "stringAttribute"; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassF.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassF.java index 7a2518f8a..43fd27580 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassF.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassF.java @@ -17,15 +17,19 @@ */ package cz.cvut.kbss.jopa.environment; -import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.jopa.environment.utils.HasUri; +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.lang.reflect.Field; import java.net.URI; -import java.util.HashSet; import java.util.Set; @OWLClass(iri = Vocabulary.c_OwlClassF) -public class OWLClassF { +public class OWLClassF implements HasUri { private static final String STR_ATT_FIELD = "secondStringAttribute"; private static final String SET_FIELD = "simpleSet"; @@ -64,9 +68,6 @@ public void setSecondStringAttribute(String secondStringAttribute) { } public Set getSimpleSet() { - if (simpleSet == null) { - this.simpleSet = new HashSet<>(); - } return simpleSet; } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassG.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassG.java index 825188a3f..a81f3157d 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassG.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassG.java @@ -20,6 +20,7 @@ import java.lang.reflect.Field; import java.net.URI; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.CascadeType; import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.Id; @@ -27,7 +28,7 @@ import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; @OWLClass(iri = Vocabulary.c_OwlClassG) -public class OWLClassG { +public class OWLClassG implements HasUri { private static final String CLS_H_FIELD = "owlClassH"; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassH.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassH.java index a6a28106a..6bdf19785 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassH.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassH.java @@ -20,6 +20,7 @@ import java.lang.reflect.Field; import java.net.URI; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.CascadeType; import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.Id; @@ -27,7 +28,7 @@ import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; @OWLClass(iri = Vocabulary.c_OwlClassH) -public class OWLClassH { +public class OWLClassH implements HasUri { private static final String CLS_A_FIELD = "owlClassA"; private static final String CLS_G_FIELD = "owlClassG"; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassI.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassI.java index 623c53458..619161fff 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassI.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassI.java @@ -17,13 +17,14 @@ */ package cz.cvut.kbss.jopa.environment; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.*; import java.lang.reflect.Field; import java.net.URI; @OWLClass(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassI") -public class OWLClassI { +public class OWLClassI implements HasUri { private static final String CLS_A_FIELD = "owlClassA"; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassJ.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassJ.java index a5f3158b6..f4b49c382 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassJ.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassJ.java @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.environment; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.*; import java.lang.reflect.Field; @@ -24,7 +25,7 @@ import java.util.Set; @OWLClass(iri = Vocabulary.c_OwlClassJ) -public class OWLClassJ { +public class OWLClassJ implements HasUri { private static final String CLS_A_FIELD = "owlClassA"; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassK.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassK.java index f89adf1f9..46107d825 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassK.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassK.java @@ -20,12 +20,13 @@ import java.lang.reflect.Field; import java.net.URI; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.Id; import cz.cvut.kbss.jopa.model.annotations.OWLClass; import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; @OWLClass(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassK") -public class OWLClassK { +public class OWLClassK implements HasUri { private static final String CLS_E_FIELD = "owlClassE"; @@ -35,7 +36,14 @@ public class OWLClassK { @OWLObjectProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#hasE") private OWLClassE owlClassE; - public URI getUri() { + public OWLClassK() { + } + + public OWLClassK(URI uri) { + this.uri = uri; + } + + public URI getUri() { return uri; } 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 f08f0279c..7d0e54bc2 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 @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.environment; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.*; import java.lang.reflect.Field; @@ -24,11 +25,8 @@ import java.util.List; import java.util.Set; -/** - * Created by ledvima1 on 12.12.14. - */ @OWLClass(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassL") -public class OWLClassL { +public class OWLClassL implements HasUri { @Id private URI uri; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassO.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassO.java index 7fefcb1a8..58d522617 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassO.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassO.java @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.environment; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.Id; import cz.cvut.kbss.jopa.model.annotations.OWLClass; import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; @@ -26,7 +27,7 @@ import java.net.URI; @OWLClass(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassO") -public class OWLClassO { +public class OWLClassO implements HasUri { // Static fields should be transient private static boolean primitiveField = false; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassP.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassP.java index 161b87464..db313ab84 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassP.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassP.java @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.environment; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.*; import java.lang.reflect.Field; @@ -27,7 +28,7 @@ import java.util.Set; @OWLClass(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassP") -public class OWLClassP { +public class OWLClassP implements HasUri { @Id(generated = true) private URI uri; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassS.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassS.java index 1b5f6252b..732a66175 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassS.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassS.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.environment; import cz.cvut.kbss.jopa.environment.listener.ParentListener; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.*; import cz.cvut.kbss.jopa.vocabulary.RDFS; @@ -30,7 +31,7 @@ @EntityListeners(ParentListener.class) @Inheritance(strategy = InheritanceType.TWO_STEP) @OWLClass(iri = Vocabulary.c_OwlClassS) -public abstract class OWLClassS implements Serializable { +public abstract class OWLClassS implements HasUri, Serializable { @Id(generated = true) private URI uri; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassT.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassT.java index 67983ebf1..1066e0c6a 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassT.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassT.java @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.environment; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.Id; import cz.cvut.kbss.jopa.model.annotations.OWLClass; import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; @@ -28,7 +29,7 @@ import java.time.LocalDateTime; @OWLClass(iri = Vocabulary.c_OwlClassT) -public class OWLClassT { +public class OWLClassT implements HasUri { @Id(generated = true) private URI uri; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassU.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassU.java index ae4f1ccb7..8b8c7b322 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassU.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassU.java @@ -21,10 +21,14 @@ 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.model.annotations.PreUpdate; +import cz.cvut.kbss.jopa.vocabulary.DC; import java.io.Serializable; import java.lang.reflect.Field; import java.net.URI; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.Set; @OWLClass(iri = Vocabulary.c_OwlClassU) @@ -39,6 +43,9 @@ public class OWLClassU implements Serializable { @OWLDataProperty(iri = Vocabulary.P_U_PLURAL_MULTILINGUAL_ATTRIBUTE) private Set pluralStringAtt; + @OWLDataProperty(iri = DC.Terms.MODIFIED) + private LocalDateTime modified; + public OWLClassU() { } @@ -70,12 +77,26 @@ public void setPluralStringAtt(Set pluralStringAtt) { this.pluralStringAtt = pluralStringAtt; } + public LocalDateTime getModified() { + return modified; + } + + public void setModified(LocalDateTime modified) { + this.modified = modified; + } + + @PreUpdate + public void preUpdate() { + this.modified = LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS); + } + @Override public String toString() { return "OWLClassU{" + "id=" + id + ", singularStringAtt=" + singularStringAtt + ", pluralStringAtt=" + pluralStringAtt + + ", modified=" + modified + '}'; } @@ -94,4 +115,8 @@ public static Field getSingularStringAttField() throws Exception { public static Field getPluralStringAttField() throws Exception { return OWLClassU.class.getDeclaredField("pluralStringAtt"); } + + public static Field getModifiedField() throws Exception { + return OWLClassU.class.getDeclaredField("modified"); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassWithQueryAttr.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassWithQueryAttr.java index 4cb5e5889..efa8e05c6 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassWithQueryAttr.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/OWLClassWithQueryAttr.java @@ -30,9 +30,10 @@ public class OWLClassWithQueryAttr { private static final String ENTITY_ATT_FIELD = "entityAttribute"; private static final String ENTITY_QUERY_ATT_FIELD = "entityQueryAttribute"; - private static final String QUERY = "PREFIX jopa:\n" + - "SELECT ?stringAttribute\n" + - "WHERE {?this jopa:B-stringAttribute ?stringAttribute}"; + private static final String QUERY = """ + PREFIX jopa: + SELECT ?stringAttribute + WHERE {?this jopa:B-stringAttribute ?stringAttribute}"""; private static final String QUERY_ENTITY = "SELECT ?entityAttribute\n" + "WHERE {?this <" + Vocabulary.P_HAS_A + "> ?entityAttribute}"; 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 ff2ca7042..e235f84d8 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 @@ -17,13 +17,14 @@ */ package cz.cvut.kbss.jopa.environment; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.*; import java.net.URI; import java.util.Set; @OWLClass(iri = Vocabulary.c_Person) -public class Person { +public class Person implements HasUri { @Id private URI uri; @@ -42,4 +43,56 @@ public class Person { @Types private Set types; + + public URI getUri() { + return uri; + } + + public void setUri(URI uri) { + this.uri = uri; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Phone getPhone() { + return phone; + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Set getTypes() { + return types; + } + + public void setTypes(Set types) { + this.types = types; + } + + public boolean isChild() { + return age != null && age < 18; + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/QMappedSuperclass.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/QMappedSuperclass.java index 4d99eb748..4126a44ed 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/QMappedSuperclass.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/QMappedSuperclass.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.environment; import cz.cvut.kbss.jopa.environment.listener.MappedSuperclassListener; +import cz.cvut.kbss.jopa.environment.utils.HasUri; import cz.cvut.kbss.jopa.model.annotations.*; import cz.cvut.kbss.jopa.vocabulary.RDFS; @@ -25,7 +26,7 @@ @EntityListeners(MappedSuperclassListener.class) @MappedSuperclass -public class QMappedSuperclass { +public class QMappedSuperclass implements HasUri { @Id(generated = true) private URI uri; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Vocabulary.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Vocabulary.java index d0b38b927..101561412 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Vocabulary.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/Vocabulary.java @@ -27,6 +27,7 @@ public class Vocabulary { public static final String c_OwlClassB = CLASS_BASE + "OWLClassB"; public static final String c_OwlClassC = CLASS_BASE + "OWLClassC"; public static final String c_OwlClassD = CLASS_BASE + "OWLClassD"; + public static final String c_OwlClassE = CLASS_BASE + "OWLClassE"; public static final String c_OwlClassF = CLASS_BASE + "OWLClassF"; public static final String c_OwlClassG = CLASS_BASE + "OWLClassG"; public static final String c_OwlClassH = CLASS_BASE + "OWLClassH"; @@ -49,6 +50,7 @@ public class Vocabulary { public static final String p_h_hasG = ATTRIBUTE_BASE + "hasG"; public static final String p_a_stringAttribute = ATTRIBUTE_BASE + "A-stringAttribute"; + public static final String p_e_stringAttribute = ATTRIBUTE_BASE + "E-stringAttribute"; public static final String p_m_booleanAttribute = ATTRIBUTE_BASE + "m-booleanAttribute"; public static final String p_m_intAttribute = ATTRIBUTE_BASE + "m-intAttribute"; public static final String p_m_longAttribute = ATTRIBUTE_BASE + "m-longAttribute"; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/ContainsSameEntities.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/ContainsSameEntities.java new file mode 100644 index 000000000..861806d91 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/ContainsSameEntities.java @@ -0,0 +1,62 @@ +/* + * JOPA + * Copyright (C) 2023 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.environment.utils; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +import java.util.Collection; +import java.util.Objects; + +/** + * Checks whether the provided collection contains the same entities as the expected one. + *

    + * The membership check is done based on entity URIs. + *

    + * Item order is not significant in the comparison, but the total number of items is. + */ +public class ContainsSameEntities extends TypeSafeMatcher> { + + private final Collection expected; + + public ContainsSameEntities(Collection expected) { + this.expected = Objects.requireNonNull(expected); + } + + @Override + protected boolean matchesSafely(Collection actual) { + if (actual == null || actual.size() != expected.size()) { + return false; + } + for (HasUri e : expected) { + if (actual.stream().noneMatch(ee -> Objects.equals(e.getUri(), ee.getUri()))) { + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendValueList("[", ", ", "]", expected); + } + + public static ContainsSameEntities containsSameEntities(Collection expected) { + return new ContainsSameEntities(expected); + } +} 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 523535fed..d2a7d6be5 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 @@ -37,11 +37,13 @@ public abstract class Generators { public static final String LANG = "en"; + public static final int DEFAULT_SIZE = 5; - private static final Set TYPES = generateTypes(DEFAULT_SIZE); private static final Random RAND = new Random(); + private static final Set TYPES = generateTypes(DEFAULT_SIZE); + private Generators() { // Private constructor } @@ -162,7 +164,7 @@ public static List generateInstances(int size) { public static Set generateTypes(int count) { final Set types = new HashSet<>(count); for (int i = 0; i < count; i++) { - types.add(Vocabulary.CLASS_BASE + i); + types.add(Vocabulary.CLASS_BASE + RAND.nextInt(10000)); } return types; } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/HasUri.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/HasUri.java new file mode 100644 index 000000000..24087e80b --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/HasUri.java @@ -0,0 +1,13 @@ +package cz.cvut.kbss.jopa.environment.utils; + +import java.net.URI; + +/** + * Marker interface for entities with URI identifier. + */ +public interface HasUri { + + URI getUri(); + + void setUri(URI uri); +} 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 ee959731a..2f894b003 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 @@ -17,17 +17,84 @@ */ package cz.cvut.kbss.jopa.environment.utils; -import cz.cvut.kbss.jopa.environment.*; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassB; +import cz.cvut.kbss.jopa.environment.OWLClassC; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.OWLClassE; +import cz.cvut.kbss.jopa.environment.OWLClassF; +import cz.cvut.kbss.jopa.environment.OWLClassG; +import cz.cvut.kbss.jopa.environment.OWLClassH; +import cz.cvut.kbss.jopa.environment.OWLClassI; +import cz.cvut.kbss.jopa.environment.OWLClassJ; +import cz.cvut.kbss.jopa.environment.OWLClassK; +import cz.cvut.kbss.jopa.environment.OWLClassL; +import cz.cvut.kbss.jopa.environment.OWLClassM; +import cz.cvut.kbss.jopa.environment.OWLClassN; +import cz.cvut.kbss.jopa.environment.OWLClassO; +import cz.cvut.kbss.jopa.environment.OWLClassP; +import cz.cvut.kbss.jopa.environment.OWLClassQ; +import cz.cvut.kbss.jopa.environment.OWLClassR; +import cz.cvut.kbss.jopa.environment.OWLClassS; +import cz.cvut.kbss.jopa.environment.OWLClassT; +import cz.cvut.kbss.jopa.environment.OWLClassU; +import cz.cvut.kbss.jopa.environment.OWLClassWithQueryAttr; +import cz.cvut.kbss.jopa.environment.OneOfEnum; +import cz.cvut.kbss.jopa.environment.Person; +import cz.cvut.kbss.jopa.environment.Phone; +import cz.cvut.kbss.jopa.environment.QMappedSuperclass; +import cz.cvut.kbss.jopa.environment.Vocabulary; +import cz.cvut.kbss.jopa.environment.ZoneOffsetConverter; import cz.cvut.kbss.jopa.environment.listener.AnotherListener; import cz.cvut.kbss.jopa.environment.listener.ConcreteListener; import cz.cvut.kbss.jopa.environment.listener.ParentListener; import cz.cvut.kbss.jopa.model.IRI; import cz.cvut.kbss.jopa.model.MultilingualString; -import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.jopa.model.annotations.CascadeType; +import cz.cvut.kbss.jopa.model.annotations.FetchType; +import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty; +import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; +import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; +import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraint; +import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraints; +import cz.cvut.kbss.jopa.model.annotations.Sequence; +import cz.cvut.kbss.jopa.model.annotations.SequenceType; +import cz.cvut.kbss.jopa.model.annotations.Sparql; import cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent; -import cz.cvut.kbss.jopa.model.metamodel.*; -import cz.cvut.kbss.jopa.oom.converter.*; +import cz.cvut.kbss.jopa.model.metamodel.AbstractAttribute; +import cz.cvut.kbss.jopa.model.metamodel.AbstractIdentifiableType; +import cz.cvut.kbss.jopa.model.metamodel.AbstractPluralAttribute; +import cz.cvut.kbss.jopa.model.metamodel.AbstractQueryAttribute; +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.CollectionType; +import cz.cvut.kbss.jopa.model.metamodel.EntityLifecycleListenerManager; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +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.model.metamodel.MappedSuperclassTypeImpl; +import cz.cvut.kbss.jopa.model.metamodel.PluralAttribute; +import cz.cvut.kbss.jopa.model.metamodel.PropertiesSpecification; +import cz.cvut.kbss.jopa.model.metamodel.QueryAttribute; +import cz.cvut.kbss.jopa.model.metamodel.SingularAttribute; +import cz.cvut.kbss.jopa.model.metamodel.SingularAttributeImpl; +import cz.cvut.kbss.jopa.model.metamodel.Type; +import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; +import cz.cvut.kbss.jopa.model.metamodel.gen.ManageableClassGenerator; +import cz.cvut.kbss.jopa.model.metamodel.gen.PersistenceContextAwareClassGenerator; +import cz.cvut.kbss.jopa.oom.converter.CustomConverterWrapper; +import cz.cvut.kbss.jopa.oom.converter.DefaultConverterWrapper; +import cz.cvut.kbss.jopa.oom.converter.ObjectOneOfEnumConverter; +import cz.cvut.kbss.jopa.oom.converter.OrdinalEnumConverter; +import cz.cvut.kbss.jopa.oom.converter.StringEnumConverter; +import cz.cvut.kbss.jopa.oom.converter.ToDoubleConverter; +import cz.cvut.kbss.jopa.oom.converter.ToIntegerConverter; +import cz.cvut.kbss.jopa.oom.converter.ToLexicalFormConverter; +import cz.cvut.kbss.jopa.oom.converter.ToLongConverter; import cz.cvut.kbss.jopa.oom.converter.datetime.LocalDateTimeConverter; +import cz.cvut.kbss.jopa.utils.Configuration; +import cz.cvut.kbss.jopa.vocabulary.DC; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -35,10 +102,24 @@ import java.net.URL; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.*; - -import static cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent.*; -import static org.mockito.Mockito.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent.POST_LOAD; +import static cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent.POST_PERSIST; +import static cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent.POST_REMOVE; +import static cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent.POST_UPDATE; +import static cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent.PRE_PERSIST; +import static cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent.PRE_REMOVE; +import static cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent.PRE_UPDATE; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** * Initializes the specified mock objects to return reasonable values. @@ -46,15 +127,30 @@ @SuppressWarnings({"unchecked", "rawtypes"}) public class MetamodelFactory { + private static PersistenceContextAwareClassGenerator instantiableTypeGenerator; + + static { + reset(); + } + private MetamodelFactory() { } + public static void reset() { + instantiableTypeGenerator = new ManageableClassGenerator(new Configuration()); + } + + public static void setInstantiableTypeGenerator(PersistenceContextAwareClassGenerator generator) { + instantiableTypeGenerator = generator; + } + /** * Initializes the specified mock objects to return reasonable values. */ public static void initOWLClassAMocks(IdentifiableEntityType etMock, AbstractAttribute strAttMock, TypesSpecification typesMock, Identifier idMock) throws Exception { when(etMock.getJavaType()).thenReturn(OWLClassA.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassA.class)); when(etMock.getPersistenceType()).thenReturn(Type.PersistenceType.ENTITY); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassA.getClassIri())); when(etMock.getAttribute(OWLClassA.getStrAttField().getName())).thenReturn(strAttMock); @@ -122,6 +218,7 @@ public static void initOWLClassBMocks(IdentifiableEntityType etMock, NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(OWLClassB.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassB.class)); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassB.getClassIri())); when(etMock.getName()).thenReturn(OWLClassB.class.getSimpleName()); when(etMock.getAttribute(OWLClassB.getStrAttField().getName())).thenReturn(strAttMock); @@ -143,15 +240,18 @@ public static void initOWLClassBMocks(IdentifiableEntityType etMock, when(strAttMock.getLanguage()).thenReturn(Generators.LANG); when(etMock.getFieldSpecification(strAttMock.getName())).thenReturn(strAttMock); when(propsMock.getJavaField()).thenReturn(OWLClassB.getPropertiesField()); + when(propsMock.getJavaType()).thenReturn(OWLClassB.getPropertiesField().getType()); when(propsMock.getName()).thenReturn(OWLClassB.getPropertiesField().getName()); when(propsMock.getDeclaringType()).thenReturn(etMock); when(propsMock.getPropertyIdentifierType()).thenReturn(String.class); when(propsMock.getPropertyValueType()).thenReturn(String.class); - when(etMock.getFieldSpecification(propsMock.getName())).thenReturn(propsMock); - when(etMock.getIdentifier()).thenReturn(idMock); + + when(idMock.getName()).thenReturn("uri"); when(idMock.getJavaField()).thenReturn(OWLClassB.class.getDeclaredField("uri")); when(idMock.getDeclaringType()).thenReturn(etMock); + when(etMock.getIdentifier()).thenReturn(idMock); + when(etMock.getFieldSpecification(idMock.getName())).thenReturn(idMock); when(etMock.getLifecycleListenerManager()).thenReturn(EntityLifecycleListenerManager.empty()); when(etMock.getDeclaredAttribute(anyString())).thenAnswer(args -> { final String name = args.getArgument(0); @@ -167,6 +267,7 @@ public static void initOWLClassCMocks(IdentifiableEntityType etMock, Identifier idMock) throws NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(OWLClassC.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassC.class)); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassC.getClassIri())); when(etMock.getName()).thenReturn(OWLClassC.class.getSimpleName()); when(etMock.getAttribute(OWLClassC.getSimpleListField().getName())).thenReturn( @@ -184,13 +285,13 @@ public static void initOWLClassCMocks(IdentifiableEntityType etMock, when(simpleListMock.getName()).thenReturn(OWLClassC.getSimpleListField().getName()); when(etMock.getFieldSpecification(simpleListMock.getName())).thenReturn(simpleListMock); String hasListAttIri = OWLClassC.getSimpleListField().getAnnotation(Sequence.class) - .ClassOWLListIRI(); + .listClassIRI(); when(simpleListMock.getSequenceType()).thenReturn(SequenceType.simple); when(simpleListMock.getCollectionType()).thenReturn(CollectionType.LIST); - when(simpleListMock.getOWLListClass()).thenReturn(IRI.create(hasListAttIri)); + when(simpleListMock.getListClassIRI()).thenReturn(IRI.create(hasListAttIri)); String hasNextIri = OWLClassC.getSimpleListField().getAnnotation(Sequence.class) - .ObjectPropertyHasNextIRI(); - when(simpleListMock.getOWLObjectPropertyHasNextIRI()).thenReturn(IRI.create(hasNextIri)); + .hasNextPropertyIRI(); + when(simpleListMock.getHasNextPropertyIRI()).thenReturn(IRI.create(hasNextIri)); when(simpleListMock.getBindableJavaType()).thenReturn(OWLClassA.class); when(simpleListMock.getPersistentAttributeType()) .thenReturn(Attribute.PersistentAttributeType.OBJECT); @@ -203,19 +304,19 @@ public static void initOWLClassCMocks(IdentifiableEntityType etMock, when(simpleListMock.getCascadeTypes()) .thenReturn(OWLClassC.getSimpleListField().getAnnotation(OWLObjectProperty.class).cascade()); - hasListAttIri = OWLClassC.getRefListField().getAnnotation(Sequence.class).ClassOWLListIRI(); + hasListAttIri = OWLClassC.getRefListField().getAnnotation(Sequence.class).listClassIRI(); when(refListMock.getFetchType()).thenReturn(FetchType.EAGER); when(refListMock.getSequenceType()).thenReturn(SequenceType.referenced); when(refListMock.getCollectionType()).thenReturn(CollectionType.LIST); - when(refListMock.getOWLListClass()).thenReturn(IRI.create(hasListAttIri)); + when(refListMock.getListClassIRI()).thenReturn(IRI.create(hasListAttIri)); when(refListMock.getName()).thenReturn(OWLClassC.getRefListField().getName()); when(etMock.getFieldSpecification(refListMock.getName())).thenReturn(refListMock); hasNextIri = OWLClassC.getRefListField().getAnnotation(Sequence.class) - .ObjectPropertyHasNextIRI(); - when(refListMock.getOWLObjectPropertyHasNextIRI()).thenReturn(IRI.create(hasNextIri)); + .hasNextPropertyIRI(); + when(refListMock.getHasNextPropertyIRI()).thenReturn(IRI.create(hasNextIri)); final String contentIri = OWLClassC.getRefListField().getAnnotation(Sequence.class) - .ObjectPropertyHasContentsIRI(); - when(refListMock.getOWLPropertyHasContentsIRI()).thenReturn(IRI.create(contentIri)); + .hasContentsPropertyIRI(); + when(refListMock.getHasContentsPropertyIRI()).thenReturn(IRI.create(contentIri)); attIri = OWLClassC.getRefListField().getAnnotation(OWLObjectProperty.class).iri(); when(refListMock.getIRI()).thenReturn(IRI.create(attIri)); when(refListMock.getBindableJavaType()).thenReturn(OWLClassA.class); @@ -248,9 +349,10 @@ public static void initOWLClassCMocks(IdentifiableEntityType etMock, * Initializes the specified mock objects to return reasonable values. */ public static void initOWLClassDMocks(IdentifiableEntityType etMock, SingularAttributeImpl clsAMock, - IdentifiableEntityType etA,Identifier idMock) + IdentifiableEntityType etA, Identifier idMock) throws NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(OWLClassD.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassD.class)); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassD.getClassIri())); when(etMock.getName()).thenReturn(OWLClassD.class.getSimpleName()); when(etMock.getAttribute(OWLClassD.getOwlClassAField().getName())).thenReturn(clsAMock); @@ -282,6 +384,7 @@ public static void initOWLClassDMocks(IdentifiableEntityType etMock, public static void initOWLClassEMocks(IdentifiableEntityType etMock, AbstractAttribute strAttMock, Identifier idMock) throws NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(OWLClassE.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassE.class)); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassE.getClassIri())); when(etMock.getName()).thenReturn(OWLClassE.class.getSimpleName()); when(etMock.getAttribute(OWLClassE.getStrAttField().getName())).thenReturn(strAttMock); @@ -298,6 +401,8 @@ public static void initOWLClassEMocks(IdentifiableEntityType etMock, when(strAttMock.getLanguage()).thenReturn(Generators.LANG); when(etMock.getIdentifier()).thenReturn(idMock); when(idMock.getJavaField()).thenReturn(OWLClassE.class.getDeclaredField("uri")); + when(idMock.isGenerated()).thenReturn(true); + when(idMock.getDeclaringType()).thenReturn(etMock); when(etMock.getLifecycleListenerManager()).thenReturn(EntityLifecycleListenerManager.empty()); } @@ -305,6 +410,7 @@ public static void initOWLClassFMocks(IdentifiableEntityType etMock, AbstractAttribute strAttMock, Identifier idMock) throws NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(OWLClassF.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassF.class)); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassF.getClassIri())); when(etMock.getName()).thenReturn(OWLClassF.class.getSimpleName()); when(etMock.getAttribute(OWLClassF.getSimpleSetField().getName())).thenReturn(setAMock); @@ -327,6 +433,7 @@ public static void initOWLClassFMocks(IdentifiableEntityType etMock, when(setAMock.getConstraints()).thenReturn(new ParticipationConstraint[]{}); when(setAMock.getDeclaringType()).thenReturn(etMock); when(setAMock.getName()).thenReturn(OWLClassF.getSimpleSetField().getName()); + when(setAMock.getFetchType()).thenReturn(FetchType.LAZY); when(etMock.getAttribute(OWLClassF.getSimpleSetField().getName())).thenReturn(setAMock); when(etMock.getFieldSpecification(OWLClassF.getSimpleSetField().getName())).thenReturn(setAMock); @@ -342,12 +449,14 @@ public static void initOWLClassFMocks(IdentifiableEntityType etMock, when(strAttMock.isInferred()).thenReturn(true); when(strAttMock.hasLanguage()).thenReturn(true); when(strAttMock.getLanguage()).thenReturn(Generators.LANG); + when(strAttMock.getFetchType()).thenReturn(FetchType.EAGER); when(etMock.getFieldSpecification(OWLClassF.getStrAttField().getName())).thenReturn(strAttMock); - when(etMock.getIdentifier()).thenReturn(idMock); when(idMock.getJavaField()).thenReturn(OWLClassF.class.getDeclaredField("uri")); when(idMock.getDeclaringType()).thenReturn(etMock); when(idMock.getName()).thenReturn("uri"); + when(etMock.getFieldSpecification(idMock.getName())).thenReturn(idMock); + when(etMock.getIdentifier()).thenReturn(idMock); when(etMock.getLifecycleListenerManager()).thenReturn(EntityLifecycleListenerManager.empty()); } @@ -355,6 +464,7 @@ public static void initOWLClassGMocks(IdentifiableEntityType etMock, IdentifiableEntityType etHMock, Identifier idMock) throws NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(OWLClassG.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassG.class)); when(etMock.getPersistenceType()).thenReturn(Type.PersistenceType.ENTITY); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassG.getClassIri())); when(etMock.getAttribute(OWLClassG.getOwlClassHField().getName())).thenReturn(clsHMock); @@ -383,6 +493,7 @@ public static void initOWLClassHMocks(IdentifiableEntityType etMock, IdentifiableEntityType etGMock, Identifier idMock) throws NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(OWLClassH.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassH.class)); when(etMock.getPersistenceType()).thenReturn(Type.PersistenceType.ENTITY); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassH.getClassIri())); when(etMock.getAttribute(OWLClassH.getOwlClassAField().getName())).thenReturn(clsAMock); @@ -420,9 +531,41 @@ public static void initOWLClassHMocks(IdentifiableEntityType etMock, when(etMock.getLifecycleListenerManager()).thenReturn(EntityLifecycleListenerManager.empty()); } + public static void initOWLClassIMocks(IdentifiableEntityType etMock, SingularAttributeImpl aAttMock, + Identifier idMock) throws NoSuchFieldException, SecurityException { + when(etMock.getJavaType()).thenReturn(OWLClassI.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassI.class)); + when(etMock.getIRI()).thenReturn(IRI.create(OWLClassI.getClassIri())); + when(etMock.getName()).thenReturn(OWLClassI.class.getSimpleName()); + when(etMock.getAttribute(OWLClassI.getOwlClassAField().getName())).thenReturn(aAttMock); + when(etMock.getAttributes()).thenReturn(Collections.singleton(aAttMock)); + when(etMock.getFieldSpecifications()) + .thenReturn(new HashSet<>(Arrays.>asList(aAttMock, idMock))); + when(aAttMock.getName()).thenReturn(OWLClassI.getOwlClassAField().getName()); + when(aAttMock.getJavaField()).thenReturn(OWLClassI.getOwlClassAField()); + when(aAttMock.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.OBJECT); + when(aAttMock.getCascadeTypes()) + .thenReturn(OWLClassI.getOwlClassAField().getAnnotation(OWLObjectProperty.class).cascade()); + final String clsAIri = OWLClassI.getOwlClassAField().getAnnotation(OWLObjectProperty.class).iri(); + when(aAttMock.getIRI()).thenReturn(IRI.create(clsAIri)); + when(aAttMock.getJavaType()).thenReturn(OWLClassA.class); + when(aAttMock.isCollection()).thenReturn(false); + when(aAttMock.getBindableJavaType()).thenReturn(OWLClassA.class); + when(aAttMock.getConstraints()).thenReturn(new ParticipationConstraint[]{}); + when(aAttMock.getDeclaringType()).thenReturn(etMock); + when(aAttMock.getFetchType()).thenReturn(FetchType.LAZY); + when(etMock.getFieldSpecification(aAttMock.getName())).thenReturn(aAttMock); + when(etMock.getIdentifier()).thenReturn(idMock); + when(idMock.getJavaField()).thenReturn(OWLClassI.class.getDeclaredField("uri")); + when(idMock.getDeclaringType()).thenReturn(etMock); + when(etMock.getFieldSpecification("uri")).thenReturn(idMock); + when(etMock.getLifecycleListenerManager()).thenReturn(EntityLifecycleListenerManager.empty()); + } + public static void initOWLClassJMocks(IdentifiableEntityType etMock, AbstractPluralAttribute setAMock, Identifier idMock) throws NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(OWLClassJ.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassJ.class)); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassJ.getClassIri())); when(etMock.getName()).thenReturn(OWLClassJ.class.getSimpleName()); when(etMock.getAttribute(OWLClassJ.getOwlClassAField().getName())).thenReturn(setAMock); @@ -451,6 +594,7 @@ public static void initOWLClassJMocks(IdentifiableEntityType etMock, public static void initOWLClassKMocks(IdentifiableEntityType etMock, AbstractAttribute clsEMock, Identifier idMock) throws NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(OWLClassK.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassK.class)); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassK.getClassIri())); when(etMock.getName()).thenReturn(OWLClassK.class.getSimpleName()); when(etMock.getAttribute(OWLClassK.getOwlClassEField().getName())).thenReturn(clsEMock); @@ -478,6 +622,7 @@ public static void initOWLClassLMocks(IdentifiableEntityType etMock, Identifier idMock) throws NoSuchFieldException { when(etMock.getJavaType()).thenReturn(OWLClassL.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassL.class)); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassL.getClassIri())); when(etMock.getName()).thenReturn(OWLClassL.class.getSimpleName()); when(etMock.getIdentifier()).thenReturn(idMock); @@ -489,7 +634,7 @@ public static void initOWLClassLMocks(IdentifiableEntityType etMock, Arrays.>asList(refListMock, simpleListMock, setMock, singleAMock))); when(etMock.getFieldSpecifications()).thenReturn(new HashSet<>( Arrays.>asList(refListMock, simpleListMock, setMock, - simpleListMock, idMock))); + simpleListMock, singleAMock, idMock))); when(refListMock.getJavaField()).thenReturn(OWLClassL.getReferencedListField()); when(refListMock.getName()).thenReturn(OWLClassL.getReferencedListField().getName()); @@ -502,13 +647,14 @@ public static void initOWLClassLMocks(IdentifiableEntityType etMock, when(refListMock.getSequenceType()).thenReturn(SequenceType.referenced); when(refListMock.getBindableJavaType()).thenReturn(OWLClassA.class); when(refListMock.isCollection()).thenReturn(true); - when(refListMock.getOWLObjectPropertyHasNextIRI()).thenReturn(IRI.create( - OWLClassL.getReferencedListField().getAnnotation(Sequence.class).ObjectPropertyHasNextIRI())); - when(refListMock.getOWLPropertyHasContentsIRI()).thenReturn(IRI.create( - OWLClassL.getReferencedListField().getAnnotation(Sequence.class).ObjectPropertyHasContentsIRI())); + when(refListMock.getHasNextPropertyIRI()).thenReturn(IRI.create( + OWLClassL.getReferencedListField().getAnnotation(Sequence.class).hasNextPropertyIRI())); + when(refListMock.getHasContentsPropertyIRI()).thenReturn(IRI.create( + OWLClassL.getReferencedListField().getAnnotation(Sequence.class).hasContentsPropertyIRI())); when(etMock.getFieldSpecification(OWLClassL.getReferencedListField().getName())).thenReturn(refListMock); when(etMock.getAttribute(OWLClassL.getReferencedListField().getName())).thenReturn(refListMock); when(refListMock.getDeclaringType()).thenReturn(etMock); + when(refListMock.getJavaType()).thenReturn(List.class); when(simpleListMock.getJavaField()).thenReturn(OWLClassL.getSimpleListField()); when(simpleListMock.getName()).thenReturn(OWLClassL.getSimpleListField().getName()); @@ -521,11 +667,12 @@ public static void initOWLClassLMocks(IdentifiableEntityType etMock, when(simpleListMock.getSequenceType()).thenReturn(SequenceType.simple); when(simpleListMock.getBindableJavaType()).thenReturn(OWLClassA.class); when(simpleListMock.isCollection()).thenReturn(true); - when(simpleListMock.getOWLObjectPropertyHasNextIRI()).thenReturn( + when(simpleListMock.getHasNextPropertyIRI()).thenReturn( IRI.create(OWLClassL.getSimpleListField().getAnnotation(OWLObjectProperty.class).iri())); when(etMock.getFieldSpecification(OWLClassL.getSimpleListField().getName())).thenReturn(simpleListMock); when(etMock.getAttribute(OWLClassL.getSimpleListField().getName())).thenReturn(simpleListMock); when(simpleListMock.getDeclaringType()).thenReturn(etMock); + when(simpleListMock.getJavaType()).thenReturn(List.class); when(setMock.getJavaField()).thenReturn(OWLClassL.getSetField()); when(setMock.getName()).thenReturn(OWLClassL.getSetField().getName()); @@ -540,6 +687,7 @@ public static void initOWLClassLMocks(IdentifiableEntityType etMock, when(etMock.getFieldSpecification(OWLClassL.getSetField().getName())).thenReturn(setMock); when(etMock.getAttribute(OWLClassL.getSetField().getName())).thenReturn(setMock); when(setMock.getDeclaringType()).thenReturn(etMock); + when(setMock.getJavaType()).thenReturn(Set.class); when(singleAMock.getJavaField()).thenReturn(OWLClassL.getSingleAField()); when(singleAMock.getName()).thenReturn(OWLClassL.getSingleAField().getName()); @@ -554,6 +702,7 @@ public static void initOWLClassLMocks(IdentifiableEntityType etMock, when(singleAMock.getDeclaringType()).thenReturn(etMock); when(etMock.getFieldSpecification(OWLClassL.getSingleAField().getName())).thenReturn(singleAMock); when(etMock.getAttribute(OWLClassL.getSingleAField().getName())).thenReturn(singleAMock); + when(singleAMock.getJavaType()).thenReturn(OWLClassA.class); when(etMock.getLifecycleListenerManager()).thenReturn(EntityLifecycleListenerManager.empty()); } @@ -569,6 +718,7 @@ public static void initOWLClassMMock(IdentifiableEntityType etMock, A Identifier idMock) throws Exception { when(etMock.getJavaType()).thenReturn(OWLClassM.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassM.class)); when(etMock.getIRI()).thenReturn(IRI.create(OWLClassM.getClassIri())); when(etMock.getName()).thenReturn(OWLClassM.class.getSimpleName()); when(etMock.getIdentifier()).thenReturn(idMock); @@ -784,6 +934,7 @@ public static void initOWLClassNMock(IdentifiableEntityType et, Singu PropertiesSpecification props, Identifier idN) throws Exception { when(et.getJavaType()).thenReturn(OWLClassN.class); + when(et.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassN.class)); when(et.getName()).thenReturn(OWLClassN.class.getSimpleName()); when(et.getIRI()).thenReturn(IRI.create(OWLClassN.getClassIri())); when(et.getIdentifier()).thenReturn(idN); @@ -859,8 +1010,11 @@ public static void initOWLClassNMock(IdentifiableEntityType et, Singu when(et.getLifecycleListenerManager()).thenReturn(EntityLifecycleListenerManager.empty()); } - public static void initOWLClassOMock(IdentifiableEntityType et, SingularAttributeImpl stringAtt, Identifier idO) + public static void initOWLClassOMock(IdentifiableEntityType et, SingularAttributeImpl stringAtt, + Identifier idO) throws Exception { + when(et.getJavaType()).thenReturn(OWLClassO.class); + when(et.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassO.class)); when(et.getIdentifier()).thenReturn(idO); when(et.getName()).thenReturn(OWLClassO.class.getSimpleName()); when(et.getIRI()).thenReturn(IRI.create(OWLClassO.getClassIri())); @@ -879,6 +1033,7 @@ public static void initOWLClassOMock(IdentifiableEntityType et, Singu when(stringAtt.hasLanguage()).thenReturn(true); when(stringAtt.getLanguage()).thenReturn(Generators.LANG); when(et.getLifecycleListenerManager()).thenReturn(EntityLifecycleListenerManager.empty()); + when(et.getFieldSpecification(anyString())).thenThrow(IllegalArgumentException.class); } public static void initOWLClassPMock(IdentifiableEntityType et, TypesSpecification types, @@ -886,6 +1041,8 @@ public static void initOWLClassPMock(IdentifiableEntityType et, Types SingularAttribute uriAtt, PluralAttribute urlsAtt, ListAttribute simpleListAtt, ListAttribute refListAtt, Identifier idP) throws Exception { + when(et.getJavaType()).thenReturn(OWLClassP.class); + when(et.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassP.class)); when(et.getIdentifier()).thenReturn(idP); when(et.getName()).thenReturn(OWLClassP.class.getSimpleName()); when(et.getIRI()).thenReturn(IRI.create(OWLClassP.getClassIri())); @@ -946,10 +1103,10 @@ public static void initOWLClassPMock(IdentifiableEntityType et, Types final Field simpleListField = OWLClassP.getSimpleListField(); when(simpleListAtt.getIRI()) .thenReturn(IRI.create(simpleListField.getAnnotation(OWLObjectProperty.class).iri())); - when(simpleListAtt.getOWLListClass()) - .thenReturn(IRI.create(simpleListField.getAnnotation(Sequence.class).ClassOWLListIRI())); - when(simpleListAtt.getOWLObjectPropertyHasNextIRI()) - .thenReturn(IRI.create(simpleListField.getAnnotation(Sequence.class).ObjectPropertyHasNextIRI())); + when(simpleListAtt.getListClassIRI()) + .thenReturn(IRI.create(simpleListField.getAnnotation(Sequence.class).listClassIRI())); + when(simpleListAtt.getHasNextPropertyIRI()) + .thenReturn(IRI.create(simpleListField.getAnnotation(Sequence.class).hasNextPropertyIRI())); when(refListAtt.getName()).thenReturn(OWLClassP.getReferencedListField().getName()); when(refListAtt.getJavaField()).thenReturn(OWLClassP.getReferencedListField()); @@ -963,12 +1120,12 @@ public static void initOWLClassPMock(IdentifiableEntityType et, Types when(refListAtt.getSequenceType()).thenReturn(SequenceType.referenced); final Field refListField = OWLClassP.getReferencedListField(); when(refListAtt.getIRI()).thenReturn(IRI.create(refListField.getAnnotation(OWLObjectProperty.class).iri())); - when(refListAtt.getOWLListClass()) - .thenReturn(IRI.create(refListField.getAnnotation(Sequence.class).ClassOWLListIRI())); - when(refListAtt.getOWLObjectPropertyHasNextIRI()) - .thenReturn(IRI.create(refListField.getAnnotation(Sequence.class).ObjectPropertyHasNextIRI())); - when(refListAtt.getOWLPropertyHasContentsIRI()) - .thenReturn(IRI.create(refListField.getAnnotation(Sequence.class).ObjectPropertyHasContentsIRI())); + when(refListAtt.getListClassIRI()) + .thenReturn(IRI.create(refListField.getAnnotation(Sequence.class).listClassIRI())); + when(refListAtt.getHasNextPropertyIRI()) + .thenReturn(IRI.create(refListField.getAnnotation(Sequence.class).hasNextPropertyIRI())); + when(refListAtt.getHasContentsPropertyIRI()) + .thenReturn(IRI.create(refListField.getAnnotation(Sequence.class).hasContentsPropertyIRI())); when(et.getLifecycleListenerManager()).thenReturn(EntityLifecycleListenerManager.empty()); } @@ -980,6 +1137,7 @@ public static void initOwlClassQMock(IdentifiableEntityType et, throws Exception { when(et.getIdentifier()).thenReturn(idQ); when(et.getJavaType()).thenReturn(OWLClassQ.class); + when(et.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassQ.class)); when(et.getName()).thenReturn(OWLClassQ.class.getSimpleName()); when(idQ.getJavaField()).thenReturn(OWLClassQ.getUriField()); when(idQ.isGenerated()).thenReturn(true); @@ -1084,6 +1242,7 @@ public static void initOwlClassSMock(IdentifiableEntityType et, Singu when(et.getIdentifier()).thenReturn(idS); when(idS.isGenerated()).thenReturn(true); when(et.getJavaType()).thenReturn(OWLClassS.class); + when(et.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassS.class)); when(idS.getJavaField()).thenReturn(OWLClassS.getUriField()); when(idS.getDeclaringType()).thenReturn(et); when(et.getIRI()).thenReturn(IRI.create(OWLClassS.getClassIri())); @@ -1124,6 +1283,7 @@ static void initOwlClassRMock(IdentifiableEntityType et, SingularAttr final Identifier id = parentEt.getIdentifier(); when(et.getIdentifier()).thenReturn(id); when(et.getJavaType()).thenReturn(OWLClassR.class); + when(et.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassR.class)); when(et.getIRI()).thenReturn(IRI.create(OWLClassR.getClassIri())); when(et.getName()).thenReturn(OWLClassR.class.getSimpleName()); final Set attributes = new HashSet<>(parentEt.getAttributes()); @@ -1187,7 +1347,8 @@ static void initOwlClassRMock(IdentifiableEntityType et, SingularAttr when(et.getLifecycleListenerManager()).thenReturn(listenerManager); } - static void initOwlClassSListeners(IdentifiableEntityType etS, ParentListener listener) throws Exception { + static void initOwlClassSListeners(IdentifiableEntityType etS, + ParentListener listener) throws Exception { final EntityLifecycleListenerManager manager = etS.getLifecycleListenerManager(); final Method addListener = EntityLifecycleListenerManager.class .getDeclaredMethod("addEntityListener", Object.class); @@ -1237,6 +1398,7 @@ static void initOwlClassTMock(IdentifiableEntityType et, SingularAttr when(et.getIdentifier()).thenReturn(id); when(id.isGenerated()).thenReturn(true); when(et.getJavaType()).thenReturn(OWLClassT.class); + when(et.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassT.class)); when(id.getJavaField()).thenReturn(OWLClassT.getUriField()); when(id.getDeclaringType()).thenReturn(et); when(et.getIRI()).thenReturn(IRI.create(OWLClassT.getClassIri())); @@ -1288,19 +1450,20 @@ static void initOwlClassTMock(IdentifiableEntityType et, SingularAttr } static void initOwlClassUMocks(IdentifiableEntityType et, SingularAttributeImpl singularStringAtt, - AbstractPluralAttribute pluralStringAtt, + AbstractPluralAttribute pluralStringAtt, SingularAttributeImpl modified, Identifier id) throws Exception { when(et.getIdentifier()).thenReturn(id); when(id.isGenerated()).thenReturn(true); when(et.getJavaType()).thenReturn(OWLClassU.class); + when(et.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(OWLClassU.class)); when(id.getJavaField()).thenReturn(OWLClassU.getIdField()); when(id.getDeclaringType()).thenReturn(et); when(id.getName()).thenReturn(OWLClassU.getIdField().getName()); when(et.getIRI()).thenReturn(IRI.create(OWLClassU.getClassIri())); when(et.getName()).thenReturn(OWLClassU.class.getSimpleName()); when(et.getFieldSpecifications()) - .thenReturn(new HashSet(Arrays.asList(singularStringAtt, pluralStringAtt, id))); - when(et.getAttributes()).thenReturn(new HashSet(Arrays.asList(singularStringAtt, pluralStringAtt))); + .thenReturn(new HashSet(Arrays.asList(singularStringAtt, pluralStringAtt, modified, id))); + when(et.getAttributes()).thenReturn(new HashSet(Arrays.asList(singularStringAtt, pluralStringAtt, modified))); when(et.getPersistenceType()).thenReturn(Type.PersistenceType.ENTITY); when(singularStringAtt.getJavaField()).thenReturn(OWLClassU.getSingularStringAttField()); @@ -1335,6 +1498,25 @@ static void initOwlClassUMocks(IdentifiableEntityType et, SingularAtt when(pluralStringAtt.getCascadeTypes()).thenReturn(new CascadeType[0]); when(pluralStringAtt.hasLanguage()).thenReturn(false); when(pluralStringAtt.getLanguage()).thenReturn(null); + + when(modified.getJavaField()).thenReturn(OWLClassU.getModifiedField()); + when(modified.getJavaType()).thenReturn(OWLClassU.getModifiedField().getType()); + when(modified.getName()).thenReturn(OWLClassU.getModifiedField().getName()); + when(et.getAttribute(OWLClassU.getModifiedField().getName())).thenReturn(modified); + when(et.getFieldSpecification(OWLClassU.getModifiedField().getName())).thenReturn(modified); + when(modified.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.DATA); + when(modified.isCollection()).thenReturn(false); + when(modified.getBindableJavaType()).thenReturn(LocalDateTime.class); + when(modified.getIRI()).thenReturn(IRI.create(DC.Terms.MODIFIED)); + when(modified.getDeclaringType()).thenReturn(et); + when(modified.getConstraints()).thenReturn(new ParticipationConstraint[0]); + when(modified.getCascadeTypes()).thenReturn(new CascadeType[0]); + when(modified.hasLanguage()).thenReturn(false); + when(modified.getLanguage()).thenReturn(null); + + final EntityLifecycleListenerManager listenerManager = new EntityLifecycleListenerManager(); + addLifecycleCallback(listenerManager, PRE_UPDATE, OWLClassU.class.getDeclaredMethod("preUpdate")); + when(et.getLifecycleListenerManager()).thenReturn(listenerManager); } static void initOWLClassWithQueryAttrMocks(IdentifiableEntityType etMock, @@ -1343,6 +1525,7 @@ static void initOWLClassWithQueryAttrMocks(IdentifiableEntityType etMock, AbstractAttribute phoneNumberAttMock, Identifier idMock) throws NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(Phone.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(Phone.class)); when(etMock.getPersistenceType()).thenReturn(Type.PersistenceType.ENTITY); when(etMock.getIRI()).thenReturn(IRI.create(Vocabulary.c_Phone)); when(etMock.getName()).thenReturn(Phone.class.getSimpleName()); @@ -1441,6 +1624,7 @@ public static void initPersonMocks(IdentifiableEntityType etMock, Abstra TypesSpecification typesMock, Identifier idMock) throws NoSuchFieldException, SecurityException { when(etMock.getJavaType()).thenReturn(Person.class); + when(etMock.getInstantiableJavaType()).thenReturn((Class) instantiableTypeGenerator.generate(Person.class)); when(etMock.getPersistenceType()).thenReturn(Type.PersistenceType.ENTITY); when(etMock.getIRI()).thenReturn(IRI.create(Vocabulary.c_Person)); when(etMock.getName()).thenReturn(Person.class.getSimpleName()); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/MetamodelMocks.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/MetamodelMocks.java index 09187700f..83ce6e458 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/MetamodelMocks.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/MetamodelMocks.java @@ -25,6 +25,7 @@ import cz.cvut.kbss.jopa.environment.OWLClassF; import cz.cvut.kbss.jopa.environment.OWLClassG; import cz.cvut.kbss.jopa.environment.OWLClassH; +import cz.cvut.kbss.jopa.environment.OWLClassI; import cz.cvut.kbss.jopa.environment.OWLClassJ; import cz.cvut.kbss.jopa.environment.OWLClassK; import cz.cvut.kbss.jopa.environment.OWLClassL; @@ -139,7 +140,7 @@ public class MetamodelMocks { @Mock private SingularAttributeImpl fStringAtt; @Mock - private AbstractPluralAttribute, OWLClassA> fSetAtt; + private SetAttributeImpl fSetAtt; @Mock private IdentifiableEntityType etH; @@ -150,6 +151,13 @@ public class MetamodelMocks { @Mock private SingularAttributeImpl hOwlClassGAtt; + @Mock + private IdentifiableEntityType etI; + @Mock + private Identifier idI; + @Mock + private SingularAttributeImpl iOwlClassAAtt; + @Mock private IdentifiableEntityType etJ; @Mock @@ -173,7 +181,7 @@ public class MetamodelMocks { @Mock private ListAttributeImpl lReferencedList; @Mock - private AbstractPluralAttribute, OWLClassA> lSetAtt; + private SetAttributeImpl lSetAtt; @Mock private SingularAttributeImpl lOwlClassAAtt; @@ -196,7 +204,7 @@ public class MetamodelMocks { @Mock private SingularAttributeImpl mOrdinalEnumAtt; @Mock - private AbstractPluralAttribute, Integer> mIntegerSetAtt; + private SetAttributeImpl mIntegerSetAtt; @Mock private SingularAttributeImpl mLexicalFormAtt; @Mock @@ -219,7 +227,7 @@ public class MetamodelMocks { @Mock private SingularAttributeImpl nStringAtt; @Mock - private AbstractPluralAttribute, String> nPluralAnnotationAtt; + private SetAttributeImpl nPluralAnnotationAtt; @Mock private PropertiesSpecification nProperties; @@ -237,7 +245,7 @@ public class MetamodelMocks { @Mock private SingularAttributeImpl pUriAtt; @Mock - private AbstractPluralAttribute, URL> pUrlsAtt; + private SetAttributeImpl pUrlsAtt; @Mock private ListAttributeImpl pSimpleList; @Mock @@ -302,7 +310,9 @@ public class MetamodelMocks { @Mock private SingularAttributeImpl uSingularStringAtt; @Mock - private AbstractPluralAttribute, MultilingualString> uPluralStringAtt; + private SetAttributeImpl uPluralStringAtt; + @Mock + private SingularAttributeImpl uModified; @Mock private IdentifiableEntityType etQA; @@ -353,6 +363,7 @@ public MetamodelMocks() throws Exception { MetamodelFactory.initOWLClassFMocks(etF, fSetAtt, fStringAtt, idF); MetamodelFactory.initOWLClassGMocks(etG, gOwlClassHAtt, etH, idG); MetamodelFactory.initOWLClassHMocks(etH, hOwlClassAAtt, hOwlClassGAtt, etA, etG, idH); + MetamodelFactory.initOWLClassIMocks(etI, iOwlClassAAtt, idI); MetamodelFactory.initOWLClassJMocks(etJ, jSetAtt, idJ); MetamodelFactory.initOWLClassKMocks(etK, kOwlClassEAtt, idK); MetamodelFactory.initOWLClassLMocks(etL, lReferencedList, lSimpleList, lSetAtt, lOwlClassAAtt, idL); @@ -373,7 +384,7 @@ public MetamodelMocks() throws Exception { MetamodelFactory.initOwlClassRMock(etR, rStringAtt, rOwlClassAAtt, etS); MetamodelFactory.initOwlClassRListeners(etR, etS, concreteListenerMock, anotherListenerMock); MetamodelFactory.initOwlClassTMock(etT, tLocalDateAtt, tLocalDateTimeAtt, tOwlClassSAtt, idT); - MetamodelFactory.initOwlClassUMocks(etU, uSingularStringAtt, uPluralStringAtt, idU); + MetamodelFactory.initOwlClassUMocks(etU, uSingularStringAtt, uPluralStringAtt, uModified, idU); MetamodelFactory.initOWLClassWithQueryAttrMocks(etQA, qaStringQueryAtt, qaStringAtt, qaEntityQueryAtt, qaEntityAtt, idQA); MetamodelFactory.initPhoneMocks(etPhone, phoneNumberAtt, idPhone); @@ -392,6 +403,7 @@ public void setMocks(Metamodel metamodel) { etMap.put(OWLClassF.class, etF); etMap.put(OWLClassG.class, etG); etMap.put(OWLClassH.class, etH); + etMap.put(OWLClassI.class, etI); etMap.put(OWLClassJ.class, etJ); etMap.put(OWLClassK.class, etK); etMap.put(OWLClassL.class, etL); @@ -454,6 +466,14 @@ public OWLClassGMetamodel forOwlClassG() { return new OWLClassGMetamodel(); } + public OWLClassHMetamodel forOwlClassH() { + return new OWLClassHMetamodel(); + } + + public OWLClassIMetamodel forOwlClassI() { + return new OWLClassIMetamodel(); + } + public OWLClassJMetamodel forOwlClassJ() { return new OWLClassJMetamodel(); } @@ -636,7 +656,20 @@ public AbstractAttribute owlClassGAtt() { public AbstractAttribute owlClassAAtt() { return MetamodelMocks.this.hOwlClassAAtt; } + } + + public class OWLClassIMetamodel { + public IdentifiableEntityType entityType() { + return MetamodelMocks.this.etI; + } + + public Identifier identifier() { + return MetamodelMocks.this.idI; + } + public SingularAttributeImpl owlClassAAtt() { + return MetamodelMocks.this.iOwlClassAAtt; + } } public class OWLClassJMetamodel { @@ -942,6 +975,10 @@ public AbstractAttribute uSingularStringAtt() { public AbstractPluralAttribute, MultilingualString> uPluralStringAtt() { return MetamodelMocks.this.uPluralStringAtt; } + + public AbstractAttribute uModified() { + return MetamodelMocks.this.uModified; + } } public class OWLClassWithQueryAttrMetamodel { diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/TestEnvironmentUtils.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/TestEnvironmentUtils.java index cfc21919f..3c170d58b 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/TestEnvironmentUtils.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/TestEnvironmentUtils.java @@ -19,9 +19,6 @@ import cz.cvut.kbss.jopa.loaders.PersistenceUnitClassFinder; import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; -import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; -import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSetImpl; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Value; @@ -35,6 +32,7 @@ import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; public final class TestEnvironmentUtils { @@ -44,10 +42,6 @@ private TestEnvironmentUtils() { throw new AssertionError(); } - public static ObjectChangeSet createObjectChangeSet(Object original, Object clone, URI context) { - return new ObjectChangeSetImpl(original, clone, new EntityDescriptor(context)); - } - /** * Returns true if the specified assertions correspond to all properties in the {@code properties} parameter. *

    @@ -57,11 +51,12 @@ public static ObjectChangeSet createObjectChangeSet(Object original, Object clon * @param assertions Actual assertions * @return Equality status. */ - public static boolean assertionsCorrespondToProperties(Map properties, Map>> assertions) { + public static boolean assertionsCorrespondToProperties(Map properties, Map>> assertions) { if (properties.size() != assertions.size()) { return false; } - for (Object entry : properties.entrySet()) { + for (Entry entry : properties.entrySet()) { + assertInstanceOf(Set.class, entry.getValue()); @SuppressWarnings("unchecked") Entry> e = (Entry>) entry; final Set> values = assertions diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/UnitOfWorkImplStub.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/UnitOfWorkImplStub.java deleted file mode 100644 index f11b8f62f..000000000 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/environment/utils/UnitOfWorkImplStub.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.environment.utils; - -import java.lang.reflect.Field; - -import cz.cvut.kbss.jopa.sessions.AbstractSession; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; - -public class UnitOfWorkImplStub extends UnitOfWorkImpl { - - private Object lastEntity; - - public UnitOfWorkImplStub(AbstractSession parent) { - super(parent); - - } - - /** - * This is the only method we need to override. - */ - @Override - public void attributeChanged(Object entity, Field field) { - this.lastEntity = entity; - } - - public Object getLastEntity() { - return lastEntity; - } - - public void setLastEntity(Object entity) { - this.lastEntity = entity; - } - - @Override - public boolean isInTransaction() { - return true; - } -} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/feature/EntityLifecycleListenersTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/feature/EntityLifecycleListenersTest.java index 2ec3e03eb..2636584cc 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/feature/EntityLifecycleListenersTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/feature/EntityLifecycleListenersTest.java @@ -31,9 +31,19 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.*; +import cz.cvut.kbss.jopa.sessions.ChangeTrackingUnitOfWork; +import cz.cvut.kbss.jopa.sessions.CloneBuilder; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; +import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; +import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; +import cz.cvut.kbss.jopa.sessions.ServerSessionStub; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.AbstractUnitOfWork; import cz.cvut.kbss.jopa.transactions.EntityTransaction; import cz.cvut.kbss.jopa.utils.Configuration; +import cz.cvut.kbss.jopa.utils.ReflectionUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -43,10 +53,20 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import java.net.URI; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * Verifies entity lifecycle listener behavior w.r.t. the JPA 2.1 spec. @@ -64,7 +84,7 @@ public class EntityLifecycleListenersTest { private MetamodelImpl metamodelMock; @Mock - private CloneBuilderImpl cloneBuilderMock; + private CloneBuilder cloneBuilderMock; @Mock private ConnectionWrapper storageMock; @@ -74,7 +94,7 @@ public class EntityLifecycleListenersTest { private MetamodelMocks mocks; - private UnitOfWorkImpl uow; + private UnitOfWork uow; private ParentListener parentListenerMock; private ConcreteListener concreteListenerMock; @@ -83,44 +103,43 @@ public class EntityLifecycleListenersTest { @BeforeEach public void setUp() throws Exception { this.descriptor = new EntityDescriptor(); - final ServerSessionStub serverSessionStub = spy(new ServerSessionStub(storageMock)); - when(serverSessionStub.getMetamodel()).thenReturn(metamodelMock); - when(serverSessionStub.getLiveObjectCache()).thenReturn(mock(CacheManager.class)); when(emMock.getTransaction()).thenReturn(transactionMock); when(transactionMock.isActive()).thenReturn(true); - when(emMock.getConfiguration()).thenReturn(new Configuration()); + final Configuration config = new Configuration(); + when(emMock.getConfiguration()).thenReturn(config); this.mocks = new MetamodelMocks(); mocks.setMocks(metamodelMock); this.parentListenerMock = mocks.forOwlClassS().parentListener(); this.concreteListenerMock = mocks.forOwlClassR().concreteListener(); this.anotherListenerMock = mocks.forOwlClassR().anotherListener(); - uow = new UnitOfWorkImpl(serverSessionStub); - uow.setEntityManager(emMock); - TestEnvironmentUtils.setMock(uow, UnitOfWorkImpl.class.getDeclaredField("cloneBuilder"), cloneBuilderMock); + final ServerSessionStub serverSessionStub = new ServerSessionStub(metamodelMock, storageMock); + this.uow = new ChangeTrackingUnitOfWork(serverSessionStub, config); + uow.begin(); + TestEnvironmentUtils.setMock(uow, AbstractUnitOfWork.class.getDeclaredField("cloneBuilder"), cloneBuilderMock); } @Test - public void prePersistLifecycleListenerIsCalledBeforeInstanceIsInsertedIntoPersistenceContext() { + public void prePersistLifecycleListenerIsCalledBeforeInstanceIsInsertedIntoPersistenceContext() throws Exception { + final Map mockMap = spy(new HashMap<>()); + TestEnvironmentUtils.setMock(uow, AbstractUnitOfWork.class.getDeclaredField("newObjectsKeyToClone"), mockMap); + final URI rId = Generators.createIndividualIdentifier(); final OWLClassR rInstance = spy(new OWLClassR()); - doAnswer(invocationOnMock -> { - final OWLClassR instance = (OWLClassR) invocationOnMock.getArguments()[1]; - instance.setUri(Generators.createIndividualIdentifier()); - return null; - }).when(storageMock).persist(null, rInstance, descriptor); + when(storageMock.generateIdentifier(metamodelMock.entity(OWLClassR.class))).thenReturn(rId); uow.registerNewObject(rInstance, descriptor); final InOrder inOrder = inOrder(rInstance, parentListenerMock, concreteListenerMock, anotherListenerMock, - storageMock); + storageMock, mockMap); inOrder.verify(parentListenerMock).prePersist(rInstance); inOrder.verify(concreteListenerMock).prePersist(rInstance); inOrder.verify(anotherListenerMock).prePersist(rInstance); inOrder.verify(rInstance).prePersist(); - inOrder.verify(storageMock).persist(any(), eq(rInstance), eq(descriptor)); + inOrder.verify(mockMap).put(rId, rInstance); } @Test public void preRemoveEntityLifecycleListenerIsCalledBeforeInstanceIsRemovedFromPersistenceContext() { final OWLClassR rOriginal = new OWLClassR(Generators.createIndividualIdentifier()); - final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); + final OWLClassR rInstance = spy(createInstance(OWLClassR.class)); + rInstance.setUri(rOriginal.getUri()); when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rInstance); uow.registerExistingObject(rOriginal, descriptor); uow.removeObject(rInstance); @@ -130,17 +149,20 @@ public void preRemoveEntityLifecycleListenerIsCalledBeforeInstanceIsRemovedFromP inOrder.verify(storageMock).remove(rInstance.getUri(), OWLClassR.class, descriptor); } + private T createInstance(Class cls) { + return ReflectionUtils.instantiateUsingDefaultConstructor(metamodelMock.entity(cls).getInstantiableJavaType()); + } + @Test - public void postPersistEntityLifecycleListenerIsCalledAfterStoragePersistOccurs() { + public void postPersistEntityLifecycleListenerIsCalledAfterInstanceIsInsertedIntoStorage() { + final URI rId = Generators.createIndividualIdentifier(); final OWLClassR rInstance = spy(new OWLClassR()); - doAnswer(invocationOnMock -> { - final OWLClassR instance = (OWLClassR) invocationOnMock.getArguments()[1]; - instance.setUri(Generators.createIndividualIdentifier()); - return null; - }).when(storageMock).persist(null, rInstance, descriptor); + when(cloneBuilderMock.buildClone(eq(rInstance), any(CloneConfiguration.class))).thenReturn(rInstance); + when(storageMock.generateIdentifier(metamodelMock.entity(OWLClassR.class))).thenReturn(rId); uow.registerNewObject(rInstance, descriptor); + uow.commit(); final InOrder inOrder = inOrder(rInstance, concreteListenerMock, storageMock); - inOrder.verify(storageMock).persist(any(), eq(rInstance), eq(descriptor)); + inOrder.verify(storageMock).persist(rId, rInstance, descriptor); inOrder.verify(concreteListenerMock).postPersist(rInstance); inOrder.verify(rInstance).postPersist(); } @@ -148,7 +170,8 @@ public void postPersistEntityLifecycleListenerIsCalledAfterStoragePersistOccurs( @Test public void postRemoveEntityLifecycleListenerIsCalledAfterStorageRemoveOccurs() { final OWLClassR rOriginal = new OWLClassR(Generators.createIndividualIdentifier()); - final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); + final OWLClassR rInstance = spy(createInstance(OWLClassR.class)); + rInstance.setUri(rOriginal.getUri()); when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rInstance); uow.registerExistingObject(rOriginal, descriptor); uow.removeObject(rInstance); @@ -161,7 +184,8 @@ public void postRemoveEntityLifecycleListenerIsCalledAfterStorageRemoveOccurs() @Test public void postLoadEntityLifecycleListenerIsCalledAfterInstanceIsLoadedIntoPersistenceContext() { final OWLClassR rOriginal = new OWLClassR(Generators.createIndividualIdentifier()); - final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); + final OWLClassR rInstance = spy(createInstance(OWLClassR.class)); + rInstance.setUri(rOriginal.getUri()); when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rInstance); when(storageMock.find(new LoadingParameters<>(OWLClassR.class, rOriginal.getUri(), descriptor))) .thenReturn(rOriginal); @@ -175,7 +199,8 @@ public void postLoadEntityLifecycleListenerIsCalledAfterInstanceIsLoadedIntoPers @Test public void postLoadEntityLifecycleListenerIsCalledAfterInstanceRefresh() { final OWLClassR rOriginal = new OWLClassR(Generators.createIndividualIdentifier()); - final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); + final OWLClassR rInstance = spy(createInstance(OWLClassR.class)); + rInstance.setUri(rOriginal.getUri()); when(storageMock.find(any(LoadingParameters.class))).thenReturn(rOriginal); when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rInstance); uow.registerExistingObject(rOriginal, descriptor); @@ -188,12 +213,15 @@ public void postLoadEntityLifecycleListenerIsCalledAfterInstanceRefresh() { @Test public void preUpdateIsCalledBeforeFieldUpdateIsMergedIntoStorage() throws Exception { final OWLClassR rOriginal = new OWLClassR(Generators.createIndividualIdentifier()); - final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); + final OWLClassR rInstance = spy(createInstance(OWLClassR.class)); + rInstance.setUri(rOriginal.getUri()); when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rInstance); + uow.getLoadStateRegistry().put(rOriginal, LoadStateDescriptorFactory.createAllLoaded(rOriginal, metamodelMock.entity(OWLClassR.class))); + uow.getLoadStateRegistry().put(rInstance, LoadStateDescriptorFactory.createAllLoaded(rOriginal, metamodelMock.entity(OWLClassR.class))); uow.registerExistingObject(rOriginal, descriptor); rInstance.setStringAtt("Update"); - // Have to call it manually, aspects do not work here + // Manually trigger attribute change handling uow.attributeChanged(rInstance, OWLClassR.getStringAttField()); final InOrder inOrder = inOrder(rInstance, concreteListenerMock, storageMock); inOrder.verify(concreteListenerMock).preUpdate(rInstance); @@ -203,12 +231,14 @@ public void preUpdateIsCalledBeforeFieldUpdateIsMergedIntoStorage() throws Excep @Test public void preUpdateIsCalledBeforeStorageMergeWhenDetachedInstanceIsMergedIntoPersistenceContext() { - final OWLClassR rOriginal = spy(new OWLClassR(Generators.createIndividualIdentifier())); + final OWLClassR rOriginal = new OWLClassR(Generators.createIndividualIdentifier()); + final OWLClassR rClone = spy(createInstance(OWLClassR.class)); + rClone.setUri(rOriginal.getUri()); final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); when(storageMock.contains(rInstance.getUri(), rInstance.getClass(), descriptor)).thenReturn(true); when(storageMock.find(any())).thenReturn(rOriginal); rInstance.setStringAtt("differentString"); - when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rOriginal); + when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rClone); final OWLClassR merged = uow.mergeDetached(rInstance, descriptor); final InOrder inOrder = inOrder(merged, concreteListenerMock, storageMock); inOrder.verify(concreteListenerMock).preUpdate(merged); @@ -218,11 +248,13 @@ public void preUpdateIsCalledBeforeStorageMergeWhenDetachedInstanceIsMergedIntoP @Test public void preUpdateIsNotCalledWhenMergedEntityHasNoChangesComparedToStorageOriginal() { - final OWLClassR rOriginal = spy(new OWLClassR(Generators.createIndividualIdentifier())); + final OWLClassR rOriginal = new OWLClassR(Generators.createIndividualIdentifier()); + final OWLClassR rClone = spy(createInstance(OWLClassR.class)); + rClone.setUri(rOriginal.getUri()); final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); when(storageMock.contains(rInstance.getUri(), rInstance.getClass(), descriptor)).thenReturn(true); when(storageMock.find(any())).thenReturn(rOriginal); - when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rOriginal); + when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rClone); final OWLClassR merged = uow.mergeDetached(rInstance, descriptor); verify(concreteListenerMock, never()).preUpdate(any()); verify(merged, never()).preUpdate(); @@ -232,12 +264,15 @@ public void preUpdateIsNotCalledWhenMergedEntityHasNoChangesComparedToStorageOri @Test public void postUpdateIsCalledAfterFieldUpdateWasMergedIntoStorage() throws Exception { final OWLClassR rOriginal = new OWLClassR(Generators.createIndividualIdentifier()); - final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); + final OWLClassR rInstance = spy(createInstance(OWLClassR.class)); + rInstance.setUri(rOriginal.getUri()); when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rInstance); + uow.getLoadStateRegistry().put(rOriginal, LoadStateDescriptorFactory.createAllLoaded(rOriginal, metamodelMock.entity(OWLClassR.class))); + uow.getLoadStateRegistry().put(rInstance, LoadStateDescriptorFactory.createAllLoaded(rOriginal, metamodelMock.entity(OWLClassR.class))); uow.registerExistingObject(rOriginal, descriptor); rInstance.setStringAtt("Update"); - // Have to call it manually, aspects do not work here + // Manually trigger attribute change handling uow.attributeChanged(rInstance, OWLClassR.getStringAttField()); final InOrder inOrder = inOrder(rInstance, concreteListenerMock, storageMock); inOrder.verify(storageMock).merge(rInstance, mocks.forOwlClassR().rStringAtt(), descriptor); @@ -247,12 +282,14 @@ public void postUpdateIsCalledAfterFieldUpdateWasMergedIntoStorage() throws Exce @Test public void postUpdateIsCalledAfterStorageMergeWhenDetachedInstanceIsMergedIntoPersistenceContext() { - final OWLClassR rOriginal = spy(new OWLClassR(Generators.createIndividualIdentifier())); + final OWLClassR rOriginal = new OWLClassR(Generators.createIndividualIdentifier()); + final OWLClassR rClone = spy(createInstance(OWLClassR.class)); + rClone.setUri(rOriginal.getUri()); final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); when(storageMock.contains(rInstance.getUri(), rInstance.getClass(), descriptor)).thenReturn(true); when(storageMock.find(any())).thenReturn(rOriginal); rInstance.setStringAtt("differentString"); - when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rOriginal); + when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rClone); final OWLClassR merged = uow.mergeDetached(rInstance, descriptor); final InOrder inOrder = inOrder(merged, concreteListenerMock, storageMock); inOrder.verify(storageMock, atLeastOnce()).merge(eq(merged), any(FieldSpecification.class), eq(descriptor)); @@ -262,11 +299,13 @@ public void postUpdateIsCalledAfterStorageMergeWhenDetachedInstanceIsMergedIntoP @Test public void postUpdateIsNotCalledWhenMergedEntityHasNoChangesComparedToStorageOriginal() { - final OWLClassR rOriginal = spy(new OWLClassR(Generators.createIndividualIdentifier())); + final OWLClassR rOriginal = new OWLClassR(Generators.createIndividualIdentifier()); + final OWLClassR rClone = spy(createInstance(OWLClassR.class)); + rClone.setUri(rOriginal.getUri()); final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); when(storageMock.contains(rInstance.getUri(), rInstance.getClass(), descriptor)).thenReturn(true); when(storageMock.find(any())).thenReturn(rOriginal); - when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rOriginal); + when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenReturn(rClone); final OWLClassR merged = uow.mergeDetached(rInstance, descriptor); verify(concreteListenerMock, never()).postUpdate(any()); verify(merged, never()).postUpdate(); @@ -276,14 +315,16 @@ public void postUpdateIsNotCalledWhenMergedEntityHasNoChangesComparedToStorageOr @Test public void postLoadListenerMethodsAreCalledOnReferencedEntitiesAsWell() { final OWLClassR rOriginal = spy(new OWLClassR(Generators.createIndividualIdentifier())); - final OWLClassR rInstance = spy(new OWLClassR(rOriginal.getUri())); + final OWLClassR rInstance = spy(createInstance(OWLClassR.class)); + rInstance.setUri(rOriginal.getUri()); final OWLClassA aOriginal = spy(Generators.generateOwlClassAInstance()); - final OWLClassA aInstance = spy(new OWLClassA(aOriginal.getUri())); + final OWLClassA aInstance = spy(createInstance(OWLClassA.class)); + aInstance.setUri(aOriginal.getUri()); rInstance.setOwlClassA(aInstance); when(cloneBuilderMock.buildClone(eq(rOriginal), any())).thenAnswer(inv -> { final CloneConfiguration config = (CloneConfiguration) inv.getArguments()[1]; - uow.registerExistingObject(aOriginal, config.getDescriptor(), config.getPostRegister()); + uow.registerExistingObject(aOriginal, new CloneRegistrationDescriptor(config.getDescriptor()).postCloneHandlers(config.getPostRegister())); return rInstance; }); when(cloneBuilderMock.buildClone(eq(aOriginal), any())).thenReturn(aInstance); @@ -300,14 +341,16 @@ public void postLoadListenerMethodsAreCalledOnReferencedEntitiesAsWell() { @Test public void postLoadListenersAreCalledOnPluralReferencesAsWell() { final OWLClassC cOriginal = new OWLClassC(Generators.createIndividualIdentifier()); - final OWLClassC cInstance = new OWLClassC(cOriginal.getUri()); + final OWLClassC cInstance = createInstance(OWLClassC.class); + cInstance.setUri(cOriginal.getUri()); final OWLClassA aOriginal = spy(Generators.generateOwlClassAInstance()); - final OWLClassA aInstance = spy(new OWLClassA(aOriginal.getUri())); + final OWLClassA aInstance = spy(createInstance(OWLClassA.class)); + aInstance.setUri(aOriginal.getUri()); cOriginal.setSimpleList(Collections.singletonList(aOriginal)); cInstance.setSimpleList(Collections.singletonList(aInstance)); when(cloneBuilderMock.buildClone(eq(cOriginal), any())).thenAnswer(inv -> { final CloneConfiguration config = (CloneConfiguration) inv.getArguments()[1]; - uow.registerExistingObject(aOriginal, config.getDescriptor(), config.getPostRegister()); + uow.registerExistingObject(aOriginal, new CloneRegistrationDescriptor(config.getDescriptor()).postCloneHandlers(config.getPostRegister())); return cInstance; }); when(cloneBuilderMock.buildClone(eq(aOriginal), any())).thenReturn(aInstance); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/loaders/EntityLoaderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/loaders/EntityLoaderTest.java index df699c531..6c88b4d0a 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/loaders/EntityLoaderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/loaders/EntityLoaderTest.java @@ -66,4 +66,4 @@ void entityLoaderIgnoresClassAnnotatedWithNonEntity() { sut.accept(NonPersistentClass.class); assertFalse(sut.getEntities().contains(NonPersistentClass.class)); } -} \ No newline at end of file +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/BeanListenerAspectTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/BeanListenerAspectTest.java deleted file mode 100644 index d4f2c09ed..000000000 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/BeanListenerAspectTest.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.model; - -import cz.cvut.kbss.jopa.environment.OWLClassA; -import cz.cvut.kbss.jopa.environment.OWLClassM; -import cz.cvut.kbss.jopa.environment.OWLClassQ; -import cz.cvut.kbss.jopa.environment.Vocabulary; -import cz.cvut.kbss.jopa.environment.utils.Generators; -import cz.cvut.kbss.jopa.exceptions.AttributeModificationForbiddenException; -import cz.cvut.kbss.jopa.model.annotations.*; -import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; -import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.model.metamodel.Identifier; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkTestBase; -import cz.cvut.kbss.jopa.vocabulary.RDFS; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.net.URI; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.*; - -class BeanListenerAspectTest extends UnitOfWorkTestBase { - - private UnitOfWorkImpl sut; - - @BeforeEach - protected void setUp() throws Exception { - super.setUp(); - this.sut = spy(uow); - } - - @Test - void setterAspectIsCalledForFieldsInOWLClass() throws Exception { - when(transactionMock.isActive()).thenReturn(true); - sut.registerNewObject(entityA, descriptor); - entityA.setStringAttribute("test"); - verify(sut).attributeChanged(entityA, OWLClassA.getStrAttField()); - } - - @Test - void setterAspectIsCalledForFieldsInMappedSuperclass() throws Exception { - when(transactionMock.isActive()).thenReturn(true); - final OWLClassQ entityQ = new OWLClassQ(); - entityQ.setUri(Generators.createIndividualIdentifier()); - sut.registerNewObject(entityQ, descriptor); - entityQ.setLabel("test"); - verify(sut).attributeChanged(entityQ, OWLClassQ.getLabelField()); - } - - @Test - void getterAspectIsCalledForFieldsInMappedSuperclass() throws Exception { - final OWLClassQ entityQ = new OWLClassQ(); - entityQ.setUri(Generators.createIndividualIdentifier()); - final FieldSpecification aSpec = metamodelMock.entity(OWLClassQ.class).getFieldSpecification("owlClassA"); - when(aSpec.getFetchType()).thenReturn(FetchType.LAZY); - final OWLClassQ clone = (OWLClassQ) sut.registerExistingObject(entityQ, descriptor); - clone.getOwlClassA(); - verify(sut).loadEntityField(clone, OWLClassQ.getOwlClassAField()); - } - - @Test - void setterAspectHandlesInstanceOfMappedSuperclassOutsideOfPersistenceContext() { - final WithMappedSuperclass instance = new WithMappedSuperclass(); - final String value = "test"; - // Just invoke setter and see if the aspect is able to handle it - instance.setLabel(value); - } - - @Test - void getterAspectHandlesInstanceOfMappedSuperclassOutsideOfPersistenceContext() { - final WithMappedSuperclass instance = new WithMappedSuperclass(); - // Just invoke getter and see if the aspect is able to handle it - assertNull(instance.getLabel()); - } - - @MappedSuperclass - public static class WithMappedSuperclass { - @OWLAnnotationProperty(iri = RDFS.LABEL) - private String label; - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - } - - @Test - void setterAspectThrowsAttributeModificationForbiddenWhenLexicalFormAttributeValueIsModified() { - when(transactionMock.isActive()).thenReturn(true); - final OWLClassM entityM = new OWLClassM(); - entityM.initializeTestValues(true); - final OWLClassM clone = (OWLClassM) sut.registerExistingObject(entityM, descriptor); - assertThrows(AttributeModificationForbiddenException.class, () -> clone.setLexicalForm("value")); - } - - @Test - void setterAspectIgnoresTransientMappedFieldSet() throws Exception { - final WithTransientMappedField instance = new WithTransientMappedField(); - when(transactionMock.isActive()).thenReturn(true); - final IdentifiableEntityType et = mock(IdentifiableEntityType.class); - doReturn(et).when(metamodelMock).entity(WithTransientMappedField.class); - final Identifier identifier = mock(Identifier.class); - when(identifier.getJavaField()).thenReturn(WithTransientMappedField.class.getDeclaredField("id")); - when(et.getIdentifier()).thenReturn(identifier); - when(et.getFieldSpecification(any())).thenThrow(IllegalArgumentException.class); - final WithTransientMappedField clone = (WithTransientMappedField) sut - .registerExistingObject(instance, descriptor); - clone.setLabel("Test"); - verify(sut, never()).attributeChanged(any(), any()); - } - - @OWLClass(iri = Vocabulary.c_OwlClassA) - public static class WithTransientMappedField { - - @Id - private URI id; - - @Transient - @OWLDataProperty(iri = RDFS.LABEL) - private String label; - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - } - - @Test - void getterAspectIgnoresTransientMappedFieldGet() throws Exception { - final WithTransientMappedField instance = new WithTransientMappedField(); - when(transactionMock.isActive()).thenReturn(true); - final IdentifiableEntityType et = mock(IdentifiableEntityType.class); - doReturn(et).when(metamodelMock).entity(WithTransientMappedField.class); - final Identifier identifier = mock(Identifier.class); - when(identifier.getJavaField()).thenReturn(WithTransientMappedField.class.getDeclaredField("id")); - when(et.getIdentifier()).thenReturn(identifier); - when(et.getFieldSpecification(any())).thenThrow(IllegalArgumentException.class); - final WithTransientMappedField clone = (WithTransientMappedField) sut - .registerExistingObject(instance, descriptor); - assertNull(clone.getLabel()); - verify(sut, never()).loadEntityField(any(), any()); - } - - @Test - void manageableInstancesAreSerializable() throws Exception { - final BeanListenerAspect.ManageableImpl sut = new BeanListenerAspect.ManageableImpl(); - sut.setPersistenceContext(this.sut); - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(sut); - oos.close(); - final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - ObjectInputStream ois = new ObjectInputStream(bais); - Object result = ois.readObject(); - assertThat(result, instanceOf(BeanListenerAspect.ManageableImpl.class)); - assertNull(((BeanListenerAspect.ManageableImpl) result).getPersistenceContext()); - } -} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImplTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImplTest.java index db689fc7b..680ef59f2 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImplTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImplTest.java @@ -21,21 +21,29 @@ import cz.cvut.kbss.jopa.environment.utils.DataSourceStub; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.sessions.CacheManager; +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +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.ontodriver.Connection; -import cz.cvut.kbss.ontodriver.Types; 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 java.util.HashMap; import java.util.Map; import java.util.function.Consumer; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class EntityManagerFactoryImplTest { @@ -50,64 +58,46 @@ class EntityManagerFactoryImplTest { @BeforeEach void setUp() { - final Map props = new HashMap<>(); - props.put(JOPAPersistenceProperties.DATA_SOURCE_CLASS, DataSourceStub.class.getName()); - props.put(JOPAPersistenceProperties.ONTOLOGY_PHYSICAL_URI_KEY, - Generators.createIndividualIdentifier().toString()); - props.put(JOPAPersistenceProperties.SCAN_PACKAGE, "cz.cvut.kbss.jopa.environment"); - this.emf = new EntityManagerFactoryImpl(props, closeListener); + this.emf = new EntityManagerFactoryImpl(getProps(), closeListener); emf.createEntityManager(); emf.getServerSession().unwrap(DataSourceStub.class).setConnection(connection); } + private Map getProps() { + return Map.of(JOPAPersistenceProperties.DATA_SOURCE_CLASS, DataSourceStub.class.getName(), + JOPAPersistenceProperties.ONTOLOGY_PHYSICAL_URI_KEY, + Generators.createIndividualIdentifier().toString(), + JOPAPersistenceProperties.SCAN_PACKAGE, "cz.cvut.kbss.jopa.environment"); + } + @Test void isLoadedReturnsTrueForManagedInstance() { - when(connection.types()).thenReturn(mock(Types.class)); final EntityManager em = emf.createEntityManager(); - try { - final OWLClassA a = Generators.generateOwlClassAInstance(); - em.persist(a); - assertTrue(emf.isLoaded(a)); - } finally { - em.close(); - } + final OWLClassA a = Generators.generateOwlClassAInstance(); + em.persist(a); + assertTrue(emf.isLoaded(a)); } @Test void isLoadedReturnsTrueForAttributeOfManagedInstance() throws Exception { - when(connection.types()).thenReturn(mock(Types.class)); final EntityManager em = emf.createEntityManager(); - try { - final OWLClassA a = Generators.generateOwlClassAInstance(); - em.persist(a); - assertTrue(emf.isLoaded(a, OWLClassA.getStrAttField().getName())); - } finally { - em.close(); - } + final OWLClassA a = Generators.generateOwlClassAInstance(); + em.persist(a); + assertTrue(emf.isLoaded(a, OWLClassA.getStrAttField().getName())); } @Test void isLoadedReturnsFalseNonNonManagedInstance() { - final EntityManager emOne = emf.createEntityManager(); - final EntityManager emTwo = emf.createEntityManager(); - try { - assertFalse(emf.isLoaded(Generators.generateOwlClassAInstance())); - } finally { - emOne.close(); - emTwo.close(); - } + emf.createEntityManager(); + emf.createEntityManager(); + assertFalse(emf.isLoaded(Generators.generateOwlClassAInstance())); } @Test void isLoadedReturnsFalseNonNonManagedInstanceWithAttribute() throws Exception { - final EntityManager emOne = emf.createEntityManager(); - final EntityManager emTwo = emf.createEntityManager(); - try { - assertFalse(emf.isLoaded(Generators.generateOwlClassAInstance(), OWLClassA.getStrAttField().getName())); - } finally { - emOne.close(); - emTwo.close(); - } + emf.createEntityManager(); + emf.createEntityManager(); + assertFalse(emf.isLoaded(Generators.generateOwlClassAInstance(), OWLClassA.getStrAttField().getName())); } @Test @@ -151,7 +141,7 @@ void getIdentifiersExtractsEntityIdentifier() { void closeClearsSecondLevelCache() { final OWLClassA instance = Generators.generateOwlClassAInstance(); final CacheManager cache = emf.unwrap(CacheManager.class); - cache.add(instance.getUri(), instance, new EntityDescriptor()); + cache.add(instance.getUri(), instance, new Descriptors(new EntityDescriptor(), new LoadStateDescriptor<>(instance, mock(EntityType.class), LoadState.LOADED))); assertTrue(cache.contains(OWLClassA.class, instance.getUri(), new EntityDescriptor())); emf.close(); assertFalse(cache.contains(OWLClassA.class, instance.getUri(), new EntityDescriptor())); @@ -162,4 +152,12 @@ void closeInvokesCloseListener() { emf.close(); verify(closeListener).accept(emf); } + + @Test + void entityManagerFactoryIsAutoCloseable() { + try (final EntityManagerFactory emf = new EntityManagerFactoryImpl(getProps(), closeListener)) { + assertTrue(emf.isOpen()); + } + verify(closeListener).accept(any(EntityManagerFactoryImpl.class)); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerImplTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerImplTest.java index acdeae770..10cae1532 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerImplTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerImplTest.java @@ -17,7 +17,13 @@ */ package cz.cvut.kbss.jopa.model; -import cz.cvut.kbss.jopa.environment.*; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassC; +import cz.cvut.kbss.jopa.environment.OWLClassE; +import cz.cvut.kbss.jopa.environment.OWLClassH; +import cz.cvut.kbss.jopa.environment.OWLClassJ; +import cz.cvut.kbss.jopa.environment.OWLClassK; +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.exceptions.OWLPersistenceException; @@ -31,10 +37,12 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityLifecycleListenerManager; import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; import cz.cvut.kbss.jopa.model.metamodel.Identifier; +import cz.cvut.kbss.jopa.sessions.ChangeTrackingUnitOfWork; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; +import cz.cvut.kbss.jopa.sessions.ServerSession; import cz.cvut.kbss.jopa.sessions.ServerSessionStub; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; -import cz.cvut.kbss.jopa.sessions.cache.DisabledCacheManager; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.AbstractUnitOfWork; import cz.cvut.kbss.jopa.transactions.EntityTransaction; import cz.cvut.kbss.jopa.utils.Configuration; import org.junit.jupiter.api.BeforeEach; @@ -52,8 +60,25 @@ import java.util.Collections; import java.util.HashSet; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +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; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -70,26 +95,27 @@ class EntityManagerImplTest { @Mock private ConnectionWrapper connectorMock; - private UnitOfWorkImpl uow; + private UnitOfWork uow; @Mock private MetamodelImpl metamodelMock; + private ServerSession serverSessionMock; + private MetamodelMocks mocks; private EntityManagerImpl em; @BeforeEach void setUp() throws Exception { - final ServerSessionStub serverSessionMock = spy(new ServerSessionStub(connectorMock)); - when(serverSessionMock.getMetamodel()).thenReturn(metamodelMock); - when(serverSessionMock.getLiveObjectCache()).thenReturn(new DisabledCacheManager()); - this.uow = spy(new UnitOfWorkImpl(serverSessionMock)); - doReturn(uow).when(serverSessionMock).acquireUnitOfWork(); + final Configuration config = new Configuration(); + this.serverSessionMock = spy(new ServerSessionStub(metamodelMock, connectorMock)); + this.uow = spy(new ChangeTrackingUnitOfWork(serverSessionMock, config)); + doReturn(uow).when(serverSessionMock).acquireUnitOfWork(any()); when(emfMock.getMetamodel()).thenReturn(metamodelMock); this.mocks = new MetamodelMocks(); mocks.setMocks(metamodelMock); - this.em = new EntityManagerImpl(emfMock, new Configuration(Collections.emptyMap()), serverSessionMock); + this.em = new EntityManagerImpl(emfMock, config, serverSessionMock); } @Test @@ -317,7 +343,7 @@ void exceptionInRefreshMarksTransactionForRollbackOnly() { @Test void exceptionInDetachMarksTransactionForRollbackOnly() { final OWLClassA a = Generators.generateOwlClassAInstance(); - doReturn(EntityManagerImpl.State.MANAGED).when(uow).getState(a); + doReturn(EntityState.MANAGED).when(uow).getState(a); doThrow(OWLPersistenceException.class).when(uow).unregisterObject(a); final EntityTransaction tx = em.getTransaction(); try { @@ -370,10 +396,10 @@ void mergeIsAbleToBreakCascadingCycle() throws Exception { final CascadeCycleTwo cloneTwo = new CascadeCycleTwo(cTwo.uri); cloneOne.two = cloneTwo; cloneTwo.one = cloneOne; - doReturn(EntityManagerImpl.State.NOT_MANAGED).when(uow).getState(cOne); - doReturn(EntityManagerImpl.State.NOT_MANAGED).when(uow).getState(cTwo); - doReturn(EntityManagerImpl.State.MANAGED).when(uow).getState(cloneOne); - doReturn(EntityManagerImpl.State.MANAGED).when(uow).getState(cloneTwo); + doReturn(EntityState.NOT_MANAGED).when(uow).getState(cOne); + doReturn(EntityState.NOT_MANAGED).when(uow).getState(cTwo); + doReturn(EntityState.MANAGED).when(uow).getState(cloneOne); + doReturn(EntityState.MANAGED).when(uow).getState(cloneTwo); doReturn(cloneOne).when(uow).mergeDetached(eq(cOne), any()); doReturn(cloneTwo).when(uow).mergeDetached(eq(cTwo), any()); doReturn(cloneOne).when(uow).getCloneForOriginal(cOne); @@ -448,10 +474,10 @@ void mergeOfNewInstancesIsAbleToBreakCascadingCycle() throws Exception { cOne.two = cTwo; cTwo.one = cOne; metamodelForCascadingTest(); - doReturn(EntityManagerImpl.State.NOT_MANAGED).doReturn(EntityManagerImpl.State.MANAGED_NEW).when(uow) - .getState(cOne); - doReturn(EntityManagerImpl.State.NOT_MANAGED).doReturn(EntityManagerImpl.State.MANAGED_NEW).when(uow) - .getState(cTwo); + doReturn(EntityState.NOT_MANAGED).doReturn(EntityState.MANAGED_NEW).when(uow) + .getState(cOne); + doReturn(EntityState.NOT_MANAGED).doReturn(EntityState.MANAGED_NEW).when(uow) + .getState(cTwo); doReturn(cOne).when(uow).mergeDetached(eq(cOne), any()); doReturn(cTwo).when(uow).mergeDetached(eq(cTwo), any()); doReturn(cOne).when(uow).getCloneForOriginal(cOne); @@ -473,12 +499,8 @@ void removeIsAbleToBreakCascadingCycle() throws Exception { final CascadeCycleTwo cloneTwo = new CascadeCycleTwo(cTwo.uri); cloneOne.two = cloneTwo; cloneTwo.one = cloneOne; - doReturn(EntityManagerImpl.State.MANAGED).doReturn(EntityManagerImpl.State.REMOVED).when(uow) - .getState(cloneOne); - doReturn(EntityManagerImpl.State.MANAGED).doReturn(EntityManagerImpl.State.REMOVED).when(uow) - .getState(cloneTwo); - doReturn(cOne).when(uow).getOriginal(cloneOne); - doReturn(cTwo).when(uow).getOriginal(cloneTwo); + doReturn(EntityState.MANAGED).doReturn(EntityState.REMOVED).when(uow).getState(cloneOne); + doReturn(EntityState.MANAGED).doReturn(EntityState.REMOVED).when(uow).getState(cloneTwo); doNothing().when(uow).removeObject(any()); em.remove(cloneOne); verify(uow).removeObject(cloneOne); @@ -624,4 +646,12 @@ void getReferenceThrowsIllegalStateWhenInvokedOnClosedEntityManager() { () -> em.getReference(OWLClassA.class, Generators.createIndividualIdentifier())); verify(uow, never()).getReference(any(), any(), any()); } + + @Test + void entityManagerIsAutoCloseable() { + try (final EntityManager em = new EntityManagerImpl(emfMock, new Configuration(Collections.emptyMap()), serverSessionMock)) { + assertTrue(em.isOpen()); + } + verify(emfMock).entityManagerClosed(any(AbstractEntityManager.class)); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/MetamodelImplInheritanceTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/MetamodelImplInheritanceTest.java index 328b4296d..79f015f36 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/MetamodelImplInheritanceTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/MetamodelImplInheritanceTest.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.model; import cz.cvut.kbss.jopa.environment.*; +import cz.cvut.kbss.jopa.environment.utils.TestLocal; import cz.cvut.kbss.jopa.exception.MetamodelInitializationException; import cz.cvut.kbss.jopa.loaders.PersistenceUnitClassFinder; import cz.cvut.kbss.jopa.model.annotations.*; @@ -171,9 +172,10 @@ void buildingMetamodelThrowsExceptionWhenInheritanceStrategyIsDeclaredOnSubtype( ex.getMessage()); } + @TestLocal @Inheritance(strategy = InheritanceType.TRY_FIRST) @OWLClass(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#SubclassWithInheritanceType") - private static final class SubclassWithInheritanceType extends OWLClassS { + public static class SubclassWithInheritanceType extends OWLClassS { } @@ -186,8 +188,9 @@ void buildingMetamodelSetsMultipleSubtypesOnSuperType() { assertTrue(supertype.getSubtypes().contains(metamodel.entity(AnotherSubclass.class))); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "AnotherSubtype") - private static class AnotherSubclass extends OWLClassS { + public static class AnotherSubclass extends OWLClassS { } /** @@ -214,11 +217,13 @@ private static class ParentWithCircular extends ParentWithId { private Set children; } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ChildWithCircular") - private static class ChildWithCircular extends ParentWithCircular { + public static class ChildWithCircular extends ParentWithCircular { } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "AnotherChildWithCircular") - private static class AnotherChildWithCircular extends ParentWithCircular { + public static class AnotherChildWithCircular extends ParentWithCircular { } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/MetamodelImplTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/MetamodelImplTest.java index 215d6329b..e4884db89 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/MetamodelImplTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/MetamodelImplTest.java @@ -19,11 +19,13 @@ import cz.cvut.kbss.jopa.environment.*; import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.environment.utils.TestLocal; import cz.cvut.kbss.jopa.exception.MetamodelInitializationException; import cz.cvut.kbss.jopa.loaders.PersistenceUnitClassFinder; import cz.cvut.kbss.jopa.model.annotations.Properties; import cz.cvut.kbss.jopa.model.annotations.*; import cz.cvut.kbss.jopa.model.metamodel.*; +import cz.cvut.kbss.jopa.proxy.lazy.gen.LazyLoadingEntityProxy; import cz.cvut.kbss.jopa.query.NamedQueryManager; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.ontodriver.config.OntoDriverProperties; @@ -41,6 +43,7 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.typeCompatibleWith; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @@ -229,9 +232,9 @@ void buildsSingleEntityWithListsSimpleList() throws Exception { simpleListField, simpleListProperty.fetch(), false, simpleListProperty.iri(), OWLClassA.class, CollectionType.LIST, simpleListProperty.cascade(), SequenceType.simple, - simpleListSequence.ClassOWLListIRI(), - simpleListSequence.ObjectPropertyHasContentsIRI(), - simpleListSequence.ObjectPropertyHasNextIRI()); + simpleListSequence.listClassIRI(), + simpleListSequence.hasContentsPropertyIRI(), + simpleListSequence.hasNextPropertyIRI()); } private void checkPluralListAttribute(FieldSpecification attribute, EntityType declaringType, String name, @@ -245,9 +248,9 @@ private void checkPluralListAttribute(FieldSpecification attribute, Entity assertThat(attribute, instanceOf(ListAttribute.class)); final ListAttribute listAttribute = (ListAttribute) attribute; assertEquals(sequenceType, listAttribute.getSequenceType()); - assertEquals(owlListClass, listAttribute.getOWLListClass().toString()); - assertEquals(hasContents, listAttribute.getOWLPropertyHasContentsIRI().toString()); - assertEquals(hasNext, listAttribute.getOWLObjectPropertyHasNextIRI().toString()); + assertEquals(owlListClass, listAttribute.getListClassIRI().toString()); + assertEquals(hasContents, listAttribute.getHasContentsPropertyIRI().toString()); + assertEquals(hasNext, listAttribute.getHasNextPropertyIRI().toString()); } @Test @@ -266,9 +269,9 @@ void buildsSingleEntityWithListsReferencedList() throws Exception { referencedListProperty.fetch(), false, referencedListProperty.iri(), OWLClassA.class, CollectionType.LIST, referencedListProperty.cascade(), SequenceType.referenced, - referencedListSequence.ClassOWLListIRI(), - referencedListSequence.ObjectPropertyHasContentsIRI(), - referencedListSequence.ObjectPropertyHasNextIRI()); + referencedListSequence.listClassIRI(), + referencedListSequence.hasContentsPropertyIRI(), + referencedListSequence.hasNextPropertyIRI()); } @Test @@ -353,8 +356,9 @@ void throwsExceptionWhenTypesFieldIsNotASet() { assertThrows(MetamodelInitializationException.class, this::getMetamodel); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithInvalidTypes") - private static class ClassWithInvalidTypes { + public static class ClassWithInvalidTypes { @Id private String id; @Types @@ -367,8 +371,9 @@ void throwsExceptionWhenPropertiesIsNotAMap() { assertThrows(MetamodelInitializationException.class, this::getMetamodel); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithInvalidProperties") - private static class ClassWithInvalidProperties { + public static class ClassWithInvalidProperties { @Id private String id; @Properties @@ -381,8 +386,9 @@ void throwExceptionWhenForClassWithInvalidIdentifier() { assertThrows(MetamodelInitializationException.class, this::getMetamodel); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithInvalidIdentifier") - private static class ClassWithInvalidIdentifier { + public static class ClassWithInvalidIdentifier { @Id private Integer id; } @@ -393,8 +399,9 @@ void throwsExceptionForClassWithoutIdentifier() { assertThrows(MetamodelInitializationException.class, this::getMetamodel); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithoutIdentifier") - private static class ClassWithoutIdentifier { + public static class ClassWithoutIdentifier { @Properties private Map> properties; } @@ -478,8 +485,9 @@ void throwsExceptionForEntityWithoutNoArgConstructor() { assertThrows(MetamodelInitializationException.class, this::getMetamodel); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithoutNoArgConstructor") - private static class ClassWithoutNoArgConstructor { + public static class ClassWithoutNoArgConstructor { @Id private URI id; @@ -495,8 +503,9 @@ void throwsExceptionForEntityWithPersistentArray() { assertThrows(MetamodelInitializationException.class, this::getMetamodel); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithArrayAttribute") - private static class ClassWithArrayAttribute { + public static class ClassWithArrayAttribute { @Id private URI id; @@ -519,8 +528,9 @@ void buildsEntityWithObjectPropertyAttributeAsUri() { assertArrayEquals(new CascadeType[0], att.getCascadeTypes()); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithOPUri") - private static class ClassWithOPUri { + public static class ClassWithOPUri { @Id private URI id; @@ -546,8 +556,9 @@ void buildsEntityWithPluralObjectPropertyAttributeAsUrls() { assertEquals(CollectionType.SET, ((PluralAttribute) att).getCollectionType()); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithOPUri") - private static class ClassWithPluralOPUrls { + public static class ClassWithPluralOPUrls { @Id private URI id; @@ -566,8 +577,9 @@ void buildsEntityWithUriTypesField() { assertEquals(URI.class, et.getTypes().getElementType()); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithUriTypes") - private static class ClassWithUriTypes { + public static class ClassWithUriTypes { @Id private URI id; @@ -593,12 +605,13 @@ void buildsEntityWithNamedQueries() { assertNotNull(queryManager.getQuery("askQuery")); } + @TestLocal @NamedNativeQueries({ @NamedNativeQuery(name = "selectAll", query = "SELECT ?x ?y ?z WHERE { ?x ?y ?z . }"), @NamedNativeQuery(name = "askQuery", query = "ASK WHERE { ?x a ?type . }") }) @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithNamedQueries") - private static class ClassWithNamedQueries { + public static class ClassWithNamedQueries { @Id private URI id; @@ -694,4 +707,24 @@ void buildForSetOfClassesBuildsOnlyEntityClassesMetamodel() { assertEquals(cls, et.getJavaType()); }); } + + @Test + void getLazyLoadingProxyReturnsLazyLoadingProxyClassForSpecifiedType() { + final Set> entityClasses = new HashSet<>(List.of(OWLClassA.class)); + final MetamodelImpl sut = new MetamodelImpl(conf); + sut.build(entityClasses); + final Class result = sut.getLazyLoadingProxy(OWLClassA.class); + assertNotNull(result); + assertThat(result, typeCompatibleWith(OWLClassA.class)); + } + + @Test + void getLazyLoadingProxyCachesGeneratedProxyClasses() { + final Set> entityClasses = new HashSet<>(List.of(OWLClassA.class)); + final MetamodelImpl sut = new MetamodelImpl(conf); + sut.build(entityClasses); + final Class resultOne = sut.getLazyLoadingProxy(OWLClassA.class); + final Class resultTwo = sut.getLazyLoadingProxy(OWLClassA.class); + assertSame(resultOne, resultTwo); + } } 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 cae1bbc19..9d0108d31 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 @@ -26,7 +26,7 @@ import cz.cvut.kbss.jopa.model.query.Query; import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.jopa.utils.Procedure; import cz.cvut.kbss.ontodriver.ResultSet; import cz.cvut.kbss.ontodriver.Statement; @@ -42,8 +42,17 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.StringContains.containsString; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +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; abstract class QueryTestBase { @@ -54,7 +63,7 @@ abstract class QueryTestBase { @Mock ConnectionWrapper connectionWrapperMock; @Mock - UnitOfWorkImpl uowMock; + UnitOfWork uowMock; @Mock Statement statementMock; @Mock 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 fddc7b0e1..47d7f5f20 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 @@ -28,7 +28,6 @@ import cz.cvut.kbss.jopa.model.query.TypedQuery; import cz.cvut.kbss.jopa.query.QueryParameter; import cz.cvut.kbss.jopa.query.parameter.ParameterValueFactory; -import cz.cvut.kbss.jopa.query.sparql.SparqlQueryHolder; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -48,8 +47,17 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +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) @MockitoSettings(strictness = Strictness.LENIENT) @@ -155,15 +163,6 @@ void throwsNoSingleResultExceptionWhenThereAreMultipleResultsForGetSingle() thro assertThrows(NoUniqueResultException.class, query::getSingleResult); } - @Test - void throwsExceptionWhenLoadingEntityWithoutUoWSet() throws Exception { - final TypedQueryImpl query = new TypedQueryImpl<>(mock(SparqlQueryHolder.class), OWLClassA.class, - connectionWrapperMock, uowMock); - query.setEnsureOpenProcedure(ensureOpenProcedure); - initDataForQuery(5); - assertThrows(IllegalStateException.class, query::getResultList); - } - @Test void askQueryReturnsSingleBoolean() throws Exception { final TypedQuery query = create(ASK_BOOLEAN_QUERY, Boolean.class); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/AbstractIdentifiableTypeTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/AbstractIdentifiableTypeTest.java index 359f90450..f14054333 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/AbstractIdentifiableTypeTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/AbstractIdentifiableTypeTest.java @@ -38,7 +38,6 @@ class AbstractIdentifiableTypeTest { private static Class cls; private static IRI classIri; - private static String className; private IdentifiableEntityType et; @@ -46,12 +45,11 @@ class AbstractIdentifiableTypeTest { static void setUpBeforeClass() { cls = OWLClassA.class; classIri = IRI.create(OWLClassA.getClassIri()); - className = OWLClassA.class.getName(); } @BeforeEach void setUp() { - this.et = new ConcreteEntityType<>(className, cls, classIri); + this.et = new ConcreteEntityType<>(cls, cls, classIri); final Identifier id = mock(Identifier.class); when(id.getName()).thenReturn(ID_NAME); et.setIdentifier(id); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/AnnotatedAccessorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/AnnotatedAccessorTest.java index 576761a0c..65fd10592 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/AnnotatedAccessorTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/AnnotatedAccessorTest.java @@ -64,12 +64,22 @@ void fromParsesBooleanIsGetterCorrectly(String methodName) throws NoSuchMethodEx AnnotatedAccessor accessor = AnnotatedAccessor.from(genericGetter); - assertEquals(genericGetter, accessor.getMethod()); assertEquals("foo", accessor.getPropertyName()); assertEquals(Boolean.class, accessor.getPropertyType()); } + @Test + void fromParsesPrimitiveBooleanIsGetterCorrectly() throws NoSuchMethodException { + Method genericGetter = MethodHolder.class.getDeclaredMethod("isBar"); + + AnnotatedAccessor accessor = AnnotatedAccessor.from(genericGetter); + + assertEquals(genericGetter, accessor.getMethod()); + assertEquals("bar", accessor.getPropertyName()); + assertEquals(boolean.class, accessor.getPropertyType()); + } + private static Stream fromThrowsExceptionOnInvalidMethodsTestValues() { return Stream.of( Arguments.arguments("getBar",new Class[0]), @@ -100,6 +110,8 @@ private abstract static class MethodHolder { abstract Boolean isFoo(); + abstract boolean isBar(); + //-- invalid getters abstract void getBar(); abstract String getA(String s); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/ClassFieldMetamodelProcessorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/ClassFieldMetamodelProcessorTest.java index 2ce04b3b0..8c6e07b1b 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/ClassFieldMetamodelProcessorTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/ClassFieldMetamodelProcessorTest.java @@ -260,9 +260,9 @@ void processFieldHandlesPrefixUseInSequenceAnnotation() throws Exception { assertInstanceOf(ListAttributeImpl.class, captor.getValue()); final ListAttributeImpl result = (ListAttributeImpl) captor.getValue(); assertEquals(SequenceType.referenced, result.getSequenceType()); - assertEquals(RDF.LIST, result.getOWLListClass().toString()); - assertEquals(RDF.FIRST, result.getOWLPropertyHasContentsIRI().toString()); - assertEquals(RDF.REST, result.getOWLObjectPropertyHasNextIRI().toString()); + assertEquals(RDF.LIST, result.getListClassIRI().toString()); + assertEquals(RDF.FIRST, result.getHasContentsPropertyIRI().toString()); + assertEquals(RDF.REST, result.getHasNextPropertyIRI().toString()); } // Test classes @@ -469,7 +469,7 @@ public static Method getSetCountMethod() throws NoSuchMethodException { @OWLClass(iri = Vocabulary.CLASS_BASE + "OWLClassWithRdfList") private static class OWLClassWithRdfList { - @Sequence(type = SequenceType.referenced, ClassOWLListIRI = "rdf:List",ObjectPropertyHasContentsIRI = "rdf:first", ObjectPropertyHasNextIRI = "rdf:rest") + @Sequence(type = SequenceType.referenced, listClassIRI = "rdf:List", hasContentsPropertyIRI = "rdf:first", hasNextPropertyIRI = "rdf:rest") @OWLDataProperty(iri = Vocabulary.ATTRIBUTE_BASE + "rdflist") private List rdfList; } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/DataPropertyAttributesTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/DataPropertyAttributesTest.java index 6c5bb6867..4e85e4ef2 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/DataPropertyAttributesTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/DataPropertyAttributesTest.java @@ -19,8 +19,10 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.OWLClassM; +import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.model.MultilingualString; import cz.cvut.kbss.jopa.model.annotations.EnumType; +import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -28,6 +30,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.Set; + import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -105,4 +109,18 @@ void resolveSetsEnumTypeToStringWhenEnumeratedAnnotationIsNotSpecifiedOnEnumValu sut.resolve(OWLClassM.getEnumAttributePropertyInfo(), metamodelBuilder, OWLClassM.Severity.class); assertEquals(EnumType.STRING, sut.getEnumType()); } + + @Test + void resolveSetsFetchTypeAlwaysToEager() throws Exception { + final DataPropertyAttributes sut = initSystemUnderTest(); + sut.resolve(PropertyInfo.from(ClassWithLazyDataProperty.class.getDeclaredField("integers")), metamodelBuilder, Set.class); + + assertEquals(FetchType.EAGER, sut.getFetchType()); + } + + private static final class ClassWithLazyDataProperty { + + @OWLDataProperty(iri = Vocabulary.ATTRIBUTE_BASE + "lazyIntegers", fetch = FetchType.LAZY) + private Set integers; + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleCallbackResolverTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleCallbackResolverTest.java index 6a7fd6aac..0d2caf16e 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleCallbackResolverTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleCallbackResolverTest.java @@ -55,9 +55,8 @@ private EntityLifecycleListenerManager resolve(AbstractIdentifiableType et) { } private AbstractIdentifiableType typeFor(Class cls) { - final String name = cls.getName(); final String iri = cls.getDeclaredAnnotation(OWLClass.class).iri(); - return new ConcreteEntityType<>(name, cls, IRI.create(iri)); + return new ConcreteEntityType<>(cls, cls, IRI.create(iri)); } @Test diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleListenerManagerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleListenerManagerTest.java index 49fe5ea96..5ff27a96f 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleListenerManagerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/EntityLifecycleListenerManagerTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.mockito.InOrder; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -214,4 +215,21 @@ void preUpdate() { EntityLifecycleListenerManagerTest.this.manager.invokePreUpdateCallbacks(this); } } + + @Test + void hasLifecycleCallbackReturnsTrueWhenEntityHasMatchingLifecycleCallback() throws Exception { + manager.addLifecycleCallback(LifecycleEvent.PRE_PERSIST, Child.class.getDeclaredMethod("prePersistChild")); + final Child instance = spy(new Child()); + assertTrue(manager.hasLifecycleCallback(LifecycleEvent.PRE_PERSIST)); + } + + @Test + void hasLifecycleCallbackReturnsTrueWhenEntityHasListenerWithMatchingLifecycleCallback() throws Exception { + final ParentListener listener = spy(new ParentListener()); + manager.addEntityListener(listener); + manager.addEntityListenerCallback(listener, LifecycleEvent.POST_LOAD, + ParentListener.class.getDeclaredMethod("postLoad", Parent.class)); + final Parent instance = new Parent(); + assertTrue(manager.hasLifecycleCallback(LifecycleEvent.POST_LOAD)); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableEntityTypeTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableEntityTypeTest.java index 22a2918dc..d5b338946 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableEntityTypeTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/IdentifiableEntityTypeTest.java @@ -28,36 +28,34 @@ class IdentifiableEntityTypeTest { private static Class cls; private static IRI classIri; - private static String className; @BeforeAll static void setUpBeforeClass() { cls = OWLClassA.class; classIri = IRI.create(OWLClassA.getClassIri()); - className = OWLClassA.class.getName(); } @Test void getAttributeThrowsIAEWhenAttributeIsNotPresent() { - final EntityType et = new ConcreteEntityType<>(className, cls, classIri); + final EntityType et = new ConcreteEntityType<>(cls, cls, classIri); assertThrows(IllegalArgumentException.class, () -> et.getAttribute("someUnknownAttribute")); } @Test void getDeclaredAttributeThrowsIAEWhenAttributeIsNotPresent() { - final EntityType et = new ConcreteEntityType<>(className, cls, classIri); + final EntityType et = new ConcreteEntityType<>(cls, cls, classIri); assertThrows(IllegalArgumentException.class, () -> et.getDeclaredAttribute("someUnknownAttribute")); } @Test void getFieldSpecificationThrowsIAEWhenAttributeIsNotPresent() { - final EntityType et = new ConcreteEntityType<>(className, cls, classIri); + final EntityType et = new ConcreteEntityType<>(cls, cls, classIri); assertThrows(IllegalArgumentException.class, () -> et.getFieldSpecification("someUnknownAttribute")); } @Test void getFieldSpecificationReturnsTypesIfNameMatches() throws Exception { - final IdentifiableEntityType et = new ConcreteEntityType<>(className, cls, classIri); + final IdentifiableEntityType et = new ConcreteEntityType<>(cls, cls, classIri); final TypesSpecification typesSpec = mock(TypesSpecification.class); when(typesSpec.getName()).thenReturn(OWLClassA.getTypesField().getName()); when(typesSpec.getJavaField()).thenReturn(OWLClassA.getTypesField()); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/ManagedClassProcessorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/ManagedClassProcessorTest.java index 3ec9cc7ee..aa282e356 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/ManagedClassProcessorTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/ManagedClassProcessorTest.java @@ -20,16 +20,25 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.QMappedSuperclass; import cz.cvut.kbss.jopa.environment.Vocabulary; +import cz.cvut.kbss.jopa.environment.utils.TestLocal; import cz.cvut.kbss.jopa.exception.MetamodelInitializationException; +import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; import cz.cvut.kbss.jopa.model.annotations.OWLClass; +import cz.cvut.kbss.jopa.utils.ChangeTrackingMode; +import cz.cvut.kbss.jopa.utils.Configuration; import org.junit.jupiter.api.Test; +import java.util.Map; import java.util.Set; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasSize; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; @SuppressWarnings("unused") class ManagedClassProcessorTest { @@ -37,12 +46,13 @@ class ManagedClassProcessorTest { @Test void processManagedTypeThrowsInitializationExceptionWhenClassIsMissingNoArgConstructor() { final MetamodelInitializationException ex = assertThrows(MetamodelInitializationException.class, - () -> ManagedClassProcessor.processManagedType(ClassWithoutNoArgConstructor.class)); - assertEquals("Class " + ClassWithoutNoArgConstructor.class + " is missing required no-arg constructor.", - ex.getMessage()); + () -> ManagedClassProcessor.processManagedType(ClassWithoutNoArgConstructor.class, new Configuration())); + assertEquals("Entity " + ClassWithoutNoArgConstructor.class + " is missing required no-arg constructor.", + ex.getMessage()); } - @OWLClass(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#ClassWithoutNoArgConstructor") + @TestLocal + @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithoutNoArgConstructor") private static class ClassWithoutNoArgConstructor { private String name; @@ -53,22 +63,30 @@ public ClassWithoutNoArgConstructor(String arg) { @Test void processManagedTypeReturnsEntityTypeForEntity() { - final TypeBuilderContext res = ManagedClassProcessor.processManagedType(OWLClassA.class); - assertTrue(res.getType() instanceof EntityType); + final TypeBuilderContext res = ManagedClassProcessor.processManagedType(OWLClassA.class, new Configuration()); + assertInstanceOf(EntityType.class, res.getType()); + } + + @Test + void processManagedTypeGeneratesInstantiableTypeForEntityClass() { + final TypeBuilderContext res = ManagedClassProcessor.processManagedType(OWLClassA.class, new Configuration()); + final Class instantiableType = res.getType().getInstantiableJavaType(); + assertTrue(OWLClassA.class.isAssignableFrom(instantiableType)); + assertNotEquals(OWLClassA.class, instantiableType); } @Test void processManagedTypeReturnsMappedSuperclassTypeForMappedSuperclass() { final TypeBuilderContext res = ManagedClassProcessor - .processManagedType(QMappedSuperclass.class); - assertTrue(res.getType() instanceof MappedSuperclassType); + .processManagedType(QMappedSuperclass.class, new Configuration()); + assertInstanceOf(MappedSuperclassType.class, res.getType()); } @Test void processManagedTypeReturnsAbstractEntityTypeTypeForInterfaces() { final TypeBuilderContext res = ManagedClassProcessor - .processManagedType(InterfaceClass.class); - assertTrue(res.getType() instanceof AbstractEntityType); + .processManagedType(InterfaceClass.class, new Configuration()); + assertInstanceOf(AbstractEntityType.class, res.getType()); } @OWLClass(iri = Vocabulary.CLASS_BASE + "Interface") @@ -78,22 +96,22 @@ private interface InterfaceClass { @Test void processManagedTypeReturnsConcreteEntityTypeTypeForClasses() { - final TypeBuilderContext res = ManagedClassProcessor - .processManagedType(OWLEntity.class); - assertTrue(res.getType() instanceof ConcreteEntityType); + final TypeBuilderContext res = ManagedClassProcessor.processManagedType(OWLEntity.class, new Configuration()); + assertInstanceOf(ConcreteEntityType.class, res.getType()); } + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "Class") - private static class OWLEntity { + public static class OWLEntity { } @Test void processManagedTypeThrowsExceptionOnNonManagedInterface() { final MetamodelInitializationException ex = assertThrows(MetamodelInitializationException.class, - () -> ManagedClassProcessor.processManagedType(NonManagedInterfaceA.class)); + () -> ManagedClassProcessor.processManagedType(NonManagedInterfaceA.class, new Configuration())); assertEquals("Type " + NonManagedInterfaceA.class + " is not a managed type.", - ex.getMessage()); + ex.getMessage()); } @Test @@ -107,7 +125,13 @@ void getManagedSuperInterfacesReturnsOnlyManagedInterfaces() { @Test void getManagedSuperClassReturnsOnlyManagedSuperClass() { Class managedSuperClass = ManagedClassProcessor.getManagedSuperClass(ChildClassWithMultipleParents.class); - assertEquals(OWLEntity.class,managedSuperClass); + assertEquals(OWLEntity.class, managedSuperClass); + } + + @Test + void processManagedTypeCreatesEntityTypeWithEntityClassAsInstantiableTypeWhenOnCommitChangeTrackingModeIsConfigured() { + final TypeBuilderContext result = ManagedClassProcessor.processManagedType(OWLClassA.class, new Configuration(Map.of(JOPAPersistenceProperties.CHANGE_TRACKING_MODE, ChangeTrackingMode.ON_COMMIT.toString()))); + assertEquals(OWLClassA.class, result.getType().getInstantiableJavaType()); } private interface NonManagedInterfaceA { @@ -127,8 +151,10 @@ private interface ManagedInterfaceA { private interface ManagedInterfaceB { } + + @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ChildClassWithMultipleParents") - private static class ChildClassWithMultipleParents extends OWLEntity implements ManagedInterfaceA, ManagedInterfaceB, NonManagedInterfaceA, NonManagedInterfaceB { + public static class ChildClassWithMultipleParents extends OWLEntity implements ManagedInterfaceA, ManagedInterfaceB, NonManagedInterfaceA, NonManagedInterfaceB { } -} \ No newline at end of file +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilderTest.java index fb7d85b3e..b1f1e4efc 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilderTest.java @@ -40,6 +40,7 @@ import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; import cz.cvut.kbss.jopa.model.annotations.PrePersist; import cz.cvut.kbss.jopa.model.annotations.Properties; +import cz.cvut.kbss.jopa.model.annotations.RDFCollection; import cz.cvut.kbss.jopa.model.annotations.Sequence; import cz.cvut.kbss.jopa.model.annotations.SequenceType; import cz.cvut.kbss.jopa.model.annotations.SparqlResultSetMapping; @@ -104,7 +105,7 @@ void buildsMetamodelOfEntityWithSingleNamespaceDeclaredOnClass() { @TestLocal @Namespace(prefix = "class", namespace = Vocabulary.CLASS_BASE) @OWLClass(iri = "class:EntityWithNamespace") - private static class EntityWithNamespace { + public static class EntityWithNamespace { @Id private URI uri; } @@ -121,7 +122,7 @@ void buildsMetamodelOfEntityWithNamespacesDeclaredOnClass() { @TestLocal @Namespaces({@Namespace(prefix = "class", namespace = Vocabulary.CLASS_BASE)}) @OWLClass(iri = "class:EntityWithNamespaces") - private static class EntityWithNamespaces { + public static class EntityWithNamespaces { @Id private URI uri; } @@ -143,7 +144,7 @@ void buildsMetamodelOfEntityWithNamespaceUsedOnAttribute() { @Namespaces({@Namespace(prefix = "dc", namespace = DC.Elements.NAMESPACE), @Namespace(prefix = "ex2", namespace = "http://www.example2.org/")}) @OWLClass(iri = "ex2:EntityWithNamespaceAttributes") - private static class EntityWithNamespaceAttributes { + public static class EntityWithNamespaceAttributes { @Id private URI uri; @@ -165,7 +166,7 @@ void buildsMetamodelOfEntityWhichUsesPackageLevelNamespace() { @TestLocal @OWLClass(iri = "ex:EntityWithNamespaceFromPackage") - private static class EntityWithNamespaceFromPackage { + public static class EntityWithNamespaceFromPackage { @Id private URI uri; } @@ -210,11 +211,11 @@ void buildMetamodelBuildsEntityWithParentLifecycleCallbacks() { .getEntityClass(ChildWithCallback.class); final EntityLifecycleListenerManager childLifecycleManager = result.getLifecycleListenerManager(); assertFalse(childLifecycleManager.getLifecycleCallbacks().isEmpty()); - assertTrue(childLifecycleManager.hasLifecycleCallback(LifecycleEvent.PRE_PERSIST)); + assertTrue(childLifecycleManager.hasEntityLifecycleCallback(LifecycleEvent.PRE_PERSIST)); assertNotNull(childLifecycleManager.getParents()); assertTrue(childLifecycleManager.getParents() .stream() - .anyMatch(parent -> parent.hasLifecycleCallback(LifecycleEvent.PRE_PERSIST))); + .anyMatch(parent -> parent.hasEntityLifecycleCallback(LifecycleEvent.PRE_PERSIST))); } @TestLocal @@ -277,7 +278,7 @@ void buildMetamodelSupportsPluralSimpleLiteralAttributes() { @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "WithPluralSimpleLiteral") - private static class WithPluralSimpleLiteral { + public static class WithPluralSimpleLiteral { @Id private URI uri; @@ -311,7 +312,7 @@ void buildMetamodelSupportsCollectionAttributes() { @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "WithCollectionAttributes") - private static class WithCollectionAttributes { + public static class WithCollectionAttributes { @Id private URI uri; @@ -348,7 +349,7 @@ void buildMetamodelThrowsInvalidFieldMappingExceptionWhenFieldIsMappedToRDFType( @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "WithInvalidTypeField") - private static class WithInvalidTypeField { + public static class WithInvalidTypeField { @Id private URI uri; @@ -384,7 +385,7 @@ private interface BParentI { @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "InterfaceChild") - private static class InterfaceChild implements AParentI, BParentI { + public static class InterfaceChild implements AParentI, BParentI { @Id private URI uri; } @@ -407,14 +408,14 @@ void buildMetamodelSupportsMultipleInheritanceInterfaceAndClass() { @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "BParent") - private static class BParent { + public static class BParent { @Id private URI uri; } @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "Child") - private static class ClassChild extends BParent implements AParentI { + public static class ClassChild extends BParent implements AParentI { } @@ -456,7 +457,7 @@ void buildMetamodelOverridesFetchTypeOfInferredTypesSpecificationToEager() { @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "WithInferredTypesAndProperties") - private static class WithInferredTypesAndProperties { + public static class WithInferredTypesAndProperties { @Id private URI uri; @@ -487,19 +488,19 @@ void buildMetamodelSupportsClassWithDataPropertyReferencedListAttribute() { final AbstractIdentifiableType et = builder.entity(ClassWithDataPropertyReferencedList.class); final ListAttribute att = et.getList("altLabels", MultilingualString.class); assertEquals(SequenceType.referenced, att.getSequenceType()); - assertEquals(RDF.REST, att.getOWLObjectPropertyHasNextIRI().toString()); - assertEquals(RDF.FIRST, att.getOWLPropertyHasContentsIRI().toString()); + assertEquals(RDF.REST, att.getHasNextPropertyIRI().toString()); + assertEquals(RDF.FIRST, att.getHasContentsPropertyIRI().toString()); } @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithDataPropertyReferencedList") - private static class ClassWithDataPropertyReferencedList { + public static class ClassWithDataPropertyReferencedList { @Id private URI uri; - @Sequence(type = SequenceType.referenced, ObjectPropertyHasNextIRI = RDF.REST, - ObjectPropertyHasContentsIRI = RDF.FIRST) + @Sequence(type = SequenceType.referenced, hasNextPropertyIRI = RDF.REST, + hasContentsPropertyIRI = RDF.FIRST) @OWLDataProperty(iri = Vocabulary.ATTRIBUTE_BASE + "dp-referenced-list") private List altLabels; } @@ -514,7 +515,7 @@ void buildMetamodelThrowsInvalidFieldMappingExceptionWhenAttemptingToUseDataProp @TestLocal @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithDataPropertySimpleList") - private static class ClassWithDataPropertySimpleList { + public static class ClassWithDataPropertySimpleList { @Id private URI uri; @@ -522,4 +523,25 @@ private static class ClassWithDataPropertySimpleList { @OWLDataProperty(iri = Vocabulary.ATTRIBUTE_BASE + "dp-referenced-list") private List altLabels; } + + @Test + void buildMetamodelSupportsRDFCollectionAttributes() { + when(finderMock.getEntities()).thenReturn(Collections.singleton(ClassWithRDFCollectionAttribute.class)); + builder.buildMetamodel(finderMock); + final AbstractIdentifiableType et = builder.entity(ClassWithRDFCollectionAttribute.class); + final ListAttribute result = et.getList("rdfCollection", Integer.class); + assertInstanceOf(RDFCollectionAttribute.class, result); + assertEquals(SequenceType.referenced, result.getSequenceType()); + } + + @TestLocal + @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithRDFCollection") + public static class ClassWithRDFCollectionAttribute { + @Id + private URI uri; + + @RDFCollection + @OWLDataProperty(iri = Vocabulary.ATTRIBUTE_BASE + "rdf-collection") + private List rdfCollection; + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/gen/ManageableClassGeneratorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/gen/ManageableClassGeneratorTest.java new file mode 100644 index 000000000..798001363 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/gen/ManageableClassGeneratorTest.java @@ -0,0 +1,85 @@ +package cz.cvut.kbss.jopa.model.metamodel.gen; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassO; +import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; +import cz.cvut.kbss.jopa.model.Manageable; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.annotations.OWLClass; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.utils.Configuration; +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.lang.reflect.Field; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +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) +@MockitoSettings(strictness = Strictness.LENIENT) +class ManageableClassGeneratorTest { + + @Mock + private UnitOfWork uow; + + private MetamodelMocks metamodelMocks; + + private final ManageableClassGenerator sut = new ManageableClassGenerator(new Configuration()); + + @BeforeEach + void setUp() throws Exception { + this.metamodelMocks = new MetamodelMocks(); + final MetamodelImpl mm = mock(MetamodelImpl.class); + metamodelMocks.setMocks(mm); + when(uow.getMetamodel()).thenReturn(mm); + } + + @Test + void generatedClassHasParentTypeAnnotations() { + final Class cls = sut.generate(OWLClassA.class); + assertNotNull(cls.getAnnotation(OWLClass.class)); + assertEquals(OWLClassA.class.getAnnotation(OWLClass.class).iri(), cls.getAnnotation(OWLClass.class).iri()); + } + + @Test + void generatedSetterInvokesPersistenceContextAttributeChangeHandler() throws Exception { + final Class cls = sut.generate(OWLClassA.class); + + final OWLClassA instance = cls.getDeclaredConstructor().newInstance(); + assertInstanceOf(Manageable.class, instance); + assertInstanceOf(OWLClassA.class, instance); + ((Manageable) instance).setPersistenceContext(uow); + when(uow.isInTransaction()).thenReturn(true); + assertEquals(uow, ((Manageable) instance).getPersistenceContext()); + instance.setStringAttribute("test value"); + assertEquals("test value", instance.getStringAttribute()); + verify(uow).attributeChanged(instance, metamodelMocks.forOwlClassA().stringAttribute()); + } + + @Test + void doesNotOverrideSetterForTransientField() throws Exception { + final Class cls = sut.generate(OWLClassO.class); + + final OWLClassO instance = cls.getDeclaredConstructor().newInstance(); + ((Manageable) instance).setPersistenceContext(uow); + when(uow.contains(instance)).thenReturn(true); + + instance.setTransientField("insignificant value"); + verify(uow, never()).attributeChanged(eq(instance), any(FieldSpecification.class)); + verify(uow, never()).attributeChanged(eq(instance), any(Field.class)); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/package-info.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/package-info.java index 65bc78f4f..f2217d7c5 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/package-info.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/package-info.java @@ -19,4 +19,4 @@ package cz.cvut.kbss.jopa.model.metamodel; import cz.cvut.kbss.jopa.model.annotations.Namespace; -import cz.cvut.kbss.jopa.model.annotations.Namespaces; \ No newline at end of file +import cz.cvut.kbss.jopa.model.annotations.Namespaces; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/AxiomDescriptorFactoryTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/AxiomDescriptorFactoryTest.java index 8752c99b9..3a65ff6be 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/AxiomDescriptorFactoryTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/AxiomDescriptorFactoryTest.java @@ -40,7 +40,7 @@ import cz.cvut.kbss.jopa.model.metamodel.CollectionType; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.ListAttribute; -import cz.cvut.kbss.jopa.sessions.LoadingParameters; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.jopa.vocabulary.RDF; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.model.Assertion; @@ -59,6 +59,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItems; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -70,7 +71,7 @@ class AxiomDescriptorFactoryTest { private static final URI CONTEXT = URI.create("http://krizik.felk.cvut.cz/ontologies/contextOne"); - private static final URI ID = URI.create("http://krizik.felk.cvut.cz/ontologies/entityX"); + private static final URI ID = URI.create(Vocabulary.INDIVIDUAL_BASE + "X"); private static URI stringAttAUri; private static URI stringAttBUri; @@ -85,9 +86,9 @@ class AxiomDescriptorFactoryTest { @BeforeAll static void setUpBeforeClass() throws Exception { - stringAttAUri = URI.create(OWLClassA.getStrAttField().getAnnotation(OWLDataProperty.class).iri()); + stringAttAUri = URI.create(Vocabulary.p_a_stringAttribute); stringAttBUri = URI.create(OWLClassB.getStrAttField().getAnnotation(OWLDataProperty.class).iri()); - owlClassAAttUri = URI.create(OWLClassD.getOwlClassAField().getAnnotation(OWLObjectProperty.class).iri()); + owlClassAAttUri = URI.create(Vocabulary.P_HAS_A); } @BeforeEach @@ -177,17 +178,17 @@ void testCreateForEntityLoadingWithAnnotationProperty() { } @Test - void createForEntityLoadingWithLazilyLoadedAttribute() { + void createForEntityLoadingAddsAssertionsForLazilyLoadedAttributesToo() { when(metamodelMocks.forOwlClassA().stringAttribute().getFetchType()).thenReturn(FetchType.LAZY); final AxiomDescriptor res = sut .createForEntityLoading(new LoadingParameters<>(OWLClassA.class, ID, descriptor), metamodelMocks.forOwlClassA().entityType()); // Types specification (class assertion) - assertEquals(1, res.getAssertions().size()); + assertEquals(2, res.getAssertions().size()); assertEquals(NamedResource.create(ID), res.getSubject()); assertThat(res.getSubjectContexts(), empty()); - assertFalse(res.getAssertions().contains( - Assertion.createDataPropertyAssertion(stringAttAUri, false))); + assertTrue(res.getAssertions().contains( + Assertion.createDataPropertyAssertion(stringAttAUri, Generators.LANG, false))); assertTrue(res.getAssertions().contains(Assertion.createClassAssertion(false))); } @@ -595,4 +596,15 @@ private static class WithDataPropertyReferencedList { @OWLDataProperty(iri = Vocabulary.ATTRIBUTE_BASE + "literalReferencedList") private List literalReferencedList; } + + @Test + void createForEntityLoadingMergesSubjectContextWithTypesContextsForClassAssertionRetrieval() { + final URI attContext = Generators.createIndividualIdentifier(); + descriptorInContext.addAttributeContext(metamodelMocks.forOwlClassA().typesSpec(), attContext); + descriptorInContext.addAttributeContext(metamodelMocks.forOwlClassA().stringAttribute(), attContext); + final LoadingParameters lp = new LoadingParameters<>(OWLClassA.class, ID, descriptorInContext); + final AxiomDescriptor result = sut.createForEntityLoading(lp, metamodelMocks.forOwlClassA().entityType()); + final Set contexts = result.getAssertionContexts(Assertion.createClassAssertion(false)); + assertThat(contexts, hasItems(attContext, CONTEXT)); + } } 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 4db43dcdf..e13e7a024 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,11 +24,16 @@ import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.model.metamodel.EntityType; -import cz.cvut.kbss.jopa.sessions.LoadingParameters; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; -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.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,8 +45,18 @@ import java.util.Collection; import java.util.Collections; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +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.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyCollection; +import static org.mockito.Mockito.eq; +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) @MockitoSettings(strictness = Strictness.LENIENT) @@ -50,7 +65,7 @@ class DefaultInstanceLoaderTest extends InstanceLoaderTestBase { private static OWLClassA entityA; private MetamodelMocks metamodelMocks; - private EntityType etAMock; + private IdentifiableEntityType etAMock; private LoadingParameters loadingParameters; @BeforeAll @@ -68,22 +83,24 @@ void setUp() throws Exception { metamodelMocks.setMocks(metamodelMock); this.etAMock = metamodelMocks.forOwlClassA().entityType(); when(descriptorFactoryMock.createForEntityLoading(loadingParameters, metamodelMocks.forOwlClassA() - .entityType())) + .entityType())) .thenReturn(axiomDescriptor); when( descriptorFactoryMock.createForFieldLoading(IDENTIFIER, metamodelMocks.forOwlClassA().typesSpec(), descriptor, metamodelMocks.forOwlClassA().entityType())).thenReturn(axiomDescriptor); entityA.setTypes(null); this.instanceLoader = DefaultInstanceLoader.builder().connection(connectionMock).metamodel(metamodelMock) - .descriptorFactory(descriptorFactoryMock).cache(cacheMock) - .entityBuilder(entityConstructorMock).build(); + .descriptorFactory(descriptorFactoryMock).cache(cacheMock) + .entityBuilder(entityConstructorMock) + .loadStateRegistry(new LoadStateDescriptorRegistry(Object::toString)) + .build(); } @Test void testLoadEntity() throws Exception { final Collection> entityAAxioms = Collections.singletonList(mock(Axiom.class)); when(connectionMock.find(axiomDescriptor)).thenReturn(entityAAxioms); - when(entityConstructorMock.reconstructEntity(IDENTIFIER, etAMock, descriptor, entityAAxioms)) + when(entityConstructorMock.reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, etAMock, descriptor, false), entityAAxioms)) .thenReturn(entityA); final OWLClassA res = instanceLoader.loadEntity(loadingParameters); @@ -111,7 +128,7 @@ void loadEntityBypassesCacheWhenConfiguredTo() throws Exception { loadingParameters.bypassCache(); final Collection> entityAAxioms = Collections.singletonList(mock(Axiom.class)); when(connectionMock.find(axiomDescriptor)).thenReturn(entityAAxioms); - when(entityConstructorMock.reconstructEntity(IDENTIFIER, etAMock, descriptor, entityAAxioms)) + when(entityConstructorMock.reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, etAMock, descriptor, false), entityAAxioms)) .thenReturn(entityA); final OWLClassA res = instanceLoader.loadEntity(loadingParameters); assertNotNull(res); @@ -188,7 +205,7 @@ void loadEntityReloadsQueryAttributesWhenInstanceIsRetrievedFromCache() { final OWLClassA res = instanceLoader.loadEntity(loadingParameters); assertEquals(entityA, res); verify(entityConstructorMock).populateQueryAttributes(entityA, etAMock); - verify(entityConstructorMock, never()).reconstructEntity(eq(loadingParameters.getIdentifier()), eq(etAMock), eq(descriptor), anyCollection()); + verify(entityConstructorMock, never()).reconstructEntity(eq(new EntityConstructor.EntityConstructionParameters<>(loadingParameters.getIdentifier(), etAMock, descriptor, 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 335f23100..ea36a5231 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 @@ -25,24 +25,41 @@ import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.ListAttribute; import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptor; -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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; import java.util.Map.Entry; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +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.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -56,6 +73,8 @@ public class EntityConstructorPluralAttributesTest { @Mock private ObjectOntologyMapperImpl mapperMock; + @Spy + private LoadStateDescriptorRegistry loadStateRegistry = new LoadStateDescriptorRegistry(Object::toString); private MetamodelMocks metamodelMocks; @@ -69,9 +88,9 @@ public class EntityConstructorPluralAttributesTest { @BeforeEach public void setUp() throws Exception { - final UnitOfWorkImpl uowMock = mock(UnitOfWorkImpl.class); + final UnitOfWork uowMock = mock(UnitOfWork.class); when(mapperMock.getUow()).thenReturn(uowMock); - when(uowMock.getQueryFactory()).thenReturn(mock(SparqlQueryFactory.class)); + when(uowMock.sparqlQueryFactory()).thenReturn(mock(SparqlQueryFactory.class)); when(mapperMock.getConfiguration()).thenReturn(new Configuration(Collections.emptyMap())); this.metamodelMocks = new MetamodelMocks(); this.simpleListMock = metamodelMocks.forOwlClassC().simpleListAtt(); @@ -81,7 +100,7 @@ public void setUp() throws Exception { this.hasSimpleListAssertion = Assertion .createObjectPropertyAssertion(simpleListProperty, simpleListMock.isInferred()); - this.constructor = new EntityConstructor(mapperMock); + this.constructor = new EntityConstructor(mapperMock, loadStateRegistry); } @Test @@ -90,7 +109,9 @@ public void reconstructsEntityWithSimpleList() { prepareMapperMockForSimpleListLoad(); final OWLClassC res = constructor - .reconstructEntity(ID, metamodelMocks.forOwlClassC().entityType(), descriptor, axioms); + .reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(ID, metamodelMocks.forOwlClassC() + .entityType(), descriptor, true), + axioms); assertNotNull(res); assertNotNull(res.getSimpleList()); @@ -102,8 +123,8 @@ public void reconstructsEntityWithSimpleList() { private void prepareMapperMockForSimpleListLoad() { for (Entry e : LIST_CONTENT.entrySet()) { when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, e.getKey(), - descriptor.getAttributeDescriptor( - simpleListMock))).thenReturn( + descriptor.getAttributeDescriptor( + simpleListMock))).thenReturn( e.getValue()); } final Collection> listAxioms = initSimpleListAxioms(); @@ -119,7 +140,7 @@ private Collection> initAxiomsForC() { } private Collection> initSimpleListAxioms() { - final URI nextElemProperty = simpleListMock.getOWLObjectPropertyHasNextIRI().toURI(); + final URI nextElemProperty = simpleListMock.getHasNextPropertyIRI().toURI(); final Collection> axioms = new ArrayList<>(LIST_CONTENT.size()); boolean first = true; URI previous = null; @@ -127,13 +148,13 @@ private Collection> initSimpleListAxioms() { final Axiom ax; if (first) { ax = new AxiomImpl<>(NamedResource.create(ID), hasSimpleListAssertion, - new Value<>(NamedResource.create(key))); + new Value<>(NamedResource.create(key))); first = false; } else { ax = new AxiomImpl<>(NamedResource.create(previous), - Assertion.createObjectPropertyAssertion(nextElemProperty, - simpleListMock.isInferred()), - new Value<>(NamedResource.create(key))); + Assertion.createObjectPropertyAssertion(nextElemProperty, + simpleListMock.isInferred()), + new Value<>(NamedResource.create(key))); } previous = key; axioms.add(ax); @@ -151,7 +172,7 @@ public void setsSimpleListLazilyLoadedFieldValue() { c.setUri(ID); assertNull(c.getSimpleList()); constructor.setFieldValue(c, metamodelMocks.forOwlClassC().simpleListAtt(), axioms, - metamodelMocks.forOwlClassC().entityType(), descriptor); + metamodelMocks.forOwlClassC().entityType(), descriptor); assertNotNull(c.getSimpleList()); assertEquals(LIST_CONTENT.size(), c.getSimpleList().size()); assertTrue(c.getSimpleList().containsAll(LIST_CONTENT.values())); 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 406847798..4c59f12fd 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 @@ -27,13 +27,19 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.utils.Configuration; -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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; @@ -43,11 +49,17 @@ import java.util.HashSet; import java.util.Set; +import static cz.cvut.kbss.jopa.oom.EntityConstructorTest.constructionConfig; import static cz.cvut.kbss.jopa.oom.EntityConstructorTest.getClassAssertionAxiomForType; import static cz.cvut.kbss.jopa.oom.EntityConstructorTest.getStringAttAssertionAxiom; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -57,8 +69,12 @@ public class EntityConstructorQueryAttributesTest { @Mock private ObjectOntologyMapperImpl mapperMock; + + @Spy + private LoadStateDescriptorRegistry loadStateRegistry = new LoadStateDescriptorRegistry(Object::toString); + @Mock - private UnitOfWorkImpl uowMock; + private UnitOfWork uowMock; @Mock private TypedQueryImpl typedQueryMock; @@ -75,10 +91,10 @@ public class EntityConstructorQueryAttributesTest { void setUp() throws Exception { when(mapperMock.getConfiguration()).thenReturn(new Configuration(Collections.emptyMap())); when(mapperMock.getUow()).thenReturn(uowMock); - when(uowMock.getQueryFactory()).thenReturn(queryFactoryMock); + when(uowMock.sparqlQueryFactory()).thenReturn(queryFactoryMock); this.mocks = new MetamodelMocks(); this.descriptor = new EntityDescriptor(); - this.constructor = new EntityConstructor(mapperMock); + this.constructor = new EntityConstructor(mapperMock, loadStateRegistry); } @Test @@ -95,8 +111,8 @@ void testReconstructEntityWithQueryAttribute() throws Exception { .when(typedQueryMock).setParameter(any(String.class), any()); doReturn(stringValue).when(typedQueryMock).getSingleResult(); - final OWLClassWithQueryAttr res = constructor.reconstructEntity(IDENTIFIER, mocks.forOwlClassWithQueryAttr() - .entityType(), descriptor, axioms); + final OWLClassWithQueryAttr res = constructor.reconstructEntity(constructionConfig(IDENTIFIER, mocks.forOwlClassWithQueryAttr() + .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(IDENTIFIER, res.getUri()); assertEquals(stringValue, res.getStringAttribute()); @@ -113,7 +129,7 @@ void testReconstructEntityWithManagedTypeQueryAttribute() throws Exception { axioms.add(getStringAttAssertionAxiom(IDENTIFIER, stringValue, OWLClassWithQueryAttr.getStrAttField())); final URI assertionUri = URI.create(OWLClassWithQueryAttr.getEntityAttField() - .getAnnotation(OWLObjectProperty.class).iri()); + .getAnnotation(OWLObjectProperty.class).iri()); final Axiom opAssertion = new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createObjectPropertyAssertion(assertionUri, false), new Value<>(NamedResource.create(identifierTwo))); @@ -135,8 +151,8 @@ void testReconstructEntityWithManagedTypeQueryAttribute() throws Exception { .when(typedQueryMock).setParameter(any(String.class), any()); doReturn(entityA).when(typedQueryMock).getSingleResult(); - final OWLClassWithQueryAttr res = constructor.reconstructEntity(IDENTIFIER, mocks.forOwlClassWithQueryAttr() - .entityType(), descriptor, axioms); + final OWLClassWithQueryAttr res = constructor.reconstructEntity(constructionConfig(IDENTIFIER, mocks.forOwlClassWithQueryAttr() + .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(IDENTIFIER, res.getUri()); verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, identifierTwo, fieldDesc); @@ -160,14 +176,14 @@ void reconstructEntityUsesReferencedEntityAttributeValuesWhenAssemblingQueryForA when(mocks.forOwlClassWithQueryAttr().entityQueryAttribute().getFetchType()).thenReturn(FetchType.LAZY); doReturn(typedQueryMock).when(queryFactoryMock) - .createNativeQuery(any(String.class), (Class) any(Class.class)); + .createNativeQuery(any(String.class), (Class) any(Class.class)); doReturn(typedQueryMock).when(typedQueryMock).setParameter(any(String.class), any()); when(typedQueryMock.hasParameter(anyString())).thenReturn(false); when(typedQueryMock.hasParameter(OWLClassWithQueryAttr.getStrAttField().getName())).thenReturn(true); doReturn(stringValue).when(typedQueryMock).getSingleResult(); - final OWLClassWithQueryAttr res = constructor.reconstructEntity(IDENTIFIER, mocks.forOwlClassWithQueryAttr() - .entityType(), descriptor, axioms); + final OWLClassWithQueryAttr res = constructor.reconstructEntity(constructionConfig(IDENTIFIER, mocks.forOwlClassWithQueryAttr() + .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(IDENTIFIER, res.getUri()); assertEquals(stringValue, res.getStringAttribute()); @@ -185,13 +201,13 @@ void reconstructEntityDoesNotUserReferencedEntityAttributeValuesWhenQueryDisable when(mocks.forOwlClassWithQueryAttr().stringQueryAttribute().enableReferencingAttributes()).thenReturn(false); doReturn(typedQueryMock).when(queryFactoryMock) - .createNativeQuery(any(String.class), (Class) any(Class.class)); + .createNativeQuery(any(String.class), (Class) any(Class.class)); doReturn(typedQueryMock).when(typedQueryMock).setParameter(any(String.class), any()); when(typedQueryMock.hasParameter(OWLClassWithQueryAttr.getStrAttField().getName())).thenReturn(true); doReturn(stringValue).when(typedQueryMock).getSingleResult(); - final OWLClassWithQueryAttr res = constructor.reconstructEntity(IDENTIFIER, mocks.forOwlClassWithQueryAttr() - .entityType(), descriptor, axioms); + final OWLClassWithQueryAttr res = constructor.reconstructEntity(constructionConfig(IDENTIFIER, mocks.forOwlClassWithQueryAttr() + .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(IDENTIFIER, res.getUri()); assertEquals(stringValue, res.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 2ebff55d8..718b5532c 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 @@ -17,41 +17,75 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.environment.*; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassB; +import cz.cvut.kbss.jopa.environment.OWLClassC; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.OWLClassJ; +import cz.cvut.kbss.jopa.environment.OWLClassL; +import cz.cvut.kbss.jopa.environment.OWLClassM; +import cz.cvut.kbss.jopa.environment.OWLClassN; +import cz.cvut.kbss.jopa.environment.OWLClassQ; +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.exceptions.CardinalityConstraintViolatedException; import cz.cvut.kbss.jopa.exceptions.IntegrityConstraintViolatedException; import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; +import cz.cvut.kbss.jopa.model.LoadState; import cz.cvut.kbss.jopa.model.SequencesVocabulary; +import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty; import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraints; 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.query.sparql.SparqlQueryFactory; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.jopa.vocabulary.DC; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; -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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import java.lang.reflect.Field; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import java.util.Set; + +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.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -66,9 +100,11 @@ class EntityConstructorTest { @Mock private ObjectOntologyMapperImpl mapperMock; @Mock - private UnitOfWorkImpl uowMock; + private UnitOfWork uowMock; @Mock private SparqlQueryFactory queryFactoryMock; + @Spy + private LoadStateDescriptorRegistry loadStateRegistry = new LoadStateDescriptorRegistry(Object::toString); private MetamodelMocks mocks; private Descriptor descriptor; @@ -79,10 +115,10 @@ class EntityConstructorTest { void setUp() throws Exception { when(mapperMock.getConfiguration()).thenReturn(new Configuration(Collections.emptyMap())); when(mapperMock.getUow()).thenReturn(uowMock); - when(uowMock.getQueryFactory()).thenReturn(queryFactoryMock); + when(uowMock.sparqlQueryFactory()).thenReturn(queryFactoryMock); this.mocks = new MetamodelMocks(); this.descriptor = new EntityDescriptor(); - this.constructor = new EntityConstructor(mapperMock); + this.constructor = new EntityConstructor(mapperMock, loadStateRegistry); } private static Set initTypes() { @@ -98,7 +134,8 @@ void testReconstructEntityWithTypesAndDataProperty() throws Exception { axioms.add(getClassAssertionAxiomForType(ID, OWLClassA.getClassIri())); axioms.add(getStringAttAssertionAxiom(ID, STRING_ATT, OWLClassA.getStrAttField())); axioms.addAll(getTypesAxiomsForOwlClassA()); - final OWLClassA res = constructor.reconstructEntity(ID, mocks.forOwlClassA().entityType(), descriptor, axioms); + final OWLClassA res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassA() + .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(ID, res.getUri()); assertEquals(STRING_ATT, res.getStringAttribute()); @@ -108,36 +145,44 @@ void testReconstructEntityWithTypesAndDataProperty() throws Exception { static Axiom getClassAssertionAxiomForType(URI uri, String type) { return new AxiomImpl<>(NamedResource.create(uri), Assertion.createClassAssertion(false), - new Value<>(URI.create(type))); + new Value<>(URI.create(type))); } static Axiom getStringAttAssertionAxiom(URI individual, String value, Field attField) { final String assertionIri = attField.getAnnotation(OWLDataProperty.class).iri(); return new AxiomImpl<>(NamedResource.create(individual), - Assertion.createDataPropertyAssertion(URI.create(assertionIri), false), - new Value<>(value)); + Assertion.createDataPropertyAssertion(URI.create(assertionIri), false), + new Value<>(value)); } private Set> getTypesAxiomsForOwlClassA() { final Set> axs = new HashSet<>(); for (String type : TYPES) { final Axiom ax = new AxiomImpl<>(NamedResource.create(ID), Assertion.createClassAssertion(false), - new Value<>(URI.create(type))); + new Value<>(URI.create(type))); axs.add(ax); } return axs; } + static EntityConstructor.EntityConstructionParameters constructionConfig(URI id, + IdentifiableEntityType et, + Descriptor descriptor) { + return new EntityConstructor.EntityConstructionParameters<>(id, et, descriptor, false); + } + @Test - void testReconstructEntityWithDataPropertyEmptyTypes() throws Exception { + void reconstructEntityWithDataPropertyAndEmptyTypesPopulatesAttributes() throws Exception { final Set> axioms = new HashSet<>(); axioms.add(getClassAssertionAxiomForType(ID, OWLClassA.getClassIri())); axioms.add(getStringAttAssertionAxiom(ID, STRING_ATT, OWLClassA.getStrAttField())); - final OWLClassA res = constructor.reconstructEntity(ID, mocks.forOwlClassA().entityType(), descriptor, axioms); + final OWLClassA res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassA() + .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(ID, res.getUri()); assertEquals(STRING_ATT, res.getStringAttribute()); - assertNull(res.getTypes()); + assertNotNull(res.getTypes()); + assertTrue(res.getTypes().isEmpty()); verify(mapperMock).registerInstance(ID, res); } @@ -148,7 +193,8 @@ void testReconstructEntityWithDataPropertyAndProperties() throws Exception { axioms.add(getStringAttAssertionAxiom(ID, STRING_ATT, OWLClassB.getStrAttField())); final Collection> properties = getProperties(); axioms.addAll(properties); - final OWLClassB res = constructor.reconstructEntity(ID, mocks.forOwlClassB().entityType(), descriptor, axioms); + final OWLClassB res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassB() + .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(ID, res.getUri()); assertEquals(STRING_ATT, res.getStringAttribute()); @@ -168,7 +214,7 @@ private Collection> getProperties() { final Axiom axOne = new AxiomImpl<>(ID_RESOURCE, Assertion .createDataPropertyAssertion(URI.create("http://someDataPropertyOne"), - false), new Value<>("SomePropertyValue")); + false), new Value<>("SomePropertyValue")); props.add(axOne); final Axiom axTwo = new AxiomImpl<>(ID_RESOURCE, Assertion.createAnnotationPropertyAssertion( @@ -177,7 +223,7 @@ private Collection> getProperties() { final Axiom axThree = new AxiomImpl<>(ID_RESOURCE, Assertion .createObjectPropertyAssertion(URI.create("http://someObjectPropertyOne"), - false), new Value<>(URI.create("http://someObjectPropertyOne"))); + false), new Value<>(URI.create("http://someObjectPropertyOne"))); props.add(axThree); return props; } @@ -187,7 +233,8 @@ void testReconstructEntityWithDataPropertiesAndEmptyProperties() throws Exceptio final Set> axioms = new HashSet<>(); axioms.add(getClassAssertionAxiomForType(ID, OWLClassB.getClassIri())); axioms.add(getStringAttAssertionAxiom(ID, STRING_ATT, OWLClassB.getStrAttField())); - final OWLClassB res = constructor.reconstructEntity(ID, mocks.forOwlClassB().entityType(), descriptor, axioms); + final OWLClassB res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassB() + .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(ID, res.getUri()); assertEquals(STRING_ATT, res.getStringAttribute()); @@ -203,9 +250,9 @@ void testReconstructEntityWithObjectProperty() throws Exception { final OWLClassA entityA = new OWLClassA(); entityA.setUri(ID_TWO); entityA.setStringAttribute(STRING_ATT); - when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, fieldDesc)) - .thenReturn(entityA); - final OWLClassD res = constructor.reconstructEntity(ID, mocks.forOwlClassD().entityType(), descriptor, axiomsD); + when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, 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); @@ -225,12 +272,11 @@ private Set> getAxiomsForD() throws Exception { private Axiom createObjectPropertyAxiomForD() throws NoSuchFieldException { return new AxiomImpl<>(NamedResource.create(ID), getClassDObjectPropertyAssertion(), - new Value<>(NamedResource.create(ID_TWO))); + new Value<>(NamedResource.create(ID_TWO))); } - private Assertion getClassDObjectPropertyAssertion() throws NoSuchFieldException { - final URI assertionUri = URI.create(OWLClassD.getOwlClassAField() - .getAnnotation(OWLObjectProperty.class).iri()); + private Assertion getClassDObjectPropertyAssertion() { + final URI assertionUri = URI.create(Vocabulary.P_HAS_A); return Assertion.createObjectPropertyAssertion(assertionUri, false); } @@ -241,7 +287,8 @@ void reconstructsEntityWithDataPropertyWhereRangeDoesNotMatch() throws Exception // Using a list and adding the incorrect range value before the right value will cause it to be processed first axioms.add(createDataPropertyAxiomWithWrongRange(OWLClassA.getStrAttField())); axioms.add(getStringAttAssertionAxiom(ID, STRING_ATT, OWLClassA.getStrAttField())); - OWLClassA entityA = constructor.reconstructEntity(ID, mocks.forOwlClassA().entityType(), descriptor, axioms); + OWLClassA entityA = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassA() + .entityType(), descriptor), axioms); assertNotNull(entityA); assertEquals(STRING_ATT, entityA.getStringAttribute()); @@ -250,7 +297,8 @@ void reconstructsEntityWithDataPropertyWhereRangeDoesNotMatch() throws Exception // Now reverse it axioms.add(getStringAttAssertionAxiom(ID, STRING_ATT, OWLClassA.getStrAttField())); axioms.add(createDataPropertyAxiomWithWrongRange(OWLClassA.getStrAttField())); - entityA = constructor.reconstructEntity(ID, mocks.forOwlClassA().entityType(), descriptor, axioms); + entityA = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassA() + .entityType(), descriptor), axioms); assertNotNull(entityA); assertEquals(STRING_ATT, entityA.getStringAttribute()); } @@ -262,7 +310,8 @@ void throwsExceptionWhenDataPropertyCardinalityRestrictionIsNotMet() throws Exce axioms.add(getStringAttAssertionAxiom(ID, STRING_ATT, OWLClassA.getStrAttField())); axioms.add(getStringAttAssertionAxiom(ID, STRING_ATT, OWLClassA.getStrAttField())); assertThrows(CardinalityConstraintViolatedException.class, - () -> constructor.reconstructEntity(ID, mocks.forOwlClassA().entityType(), descriptor, axioms)); + () -> constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassA() + .entityType(), descriptor), axioms)); } private Axiom createDataPropertyAxiomWithWrongRange(Field valueField) { @@ -279,7 +328,8 @@ void reconstructsEntityWithObjectPropertyWhereRangeDoesNotMatch() throws Excepti 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); - final OWLClassD res = constructor.reconstructEntity(ID, mocks.forOwlClassD().entityType(), descriptor, axioms); + final OWLClassD res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassD() + .entityType(), descriptor), axioms); assertNotNull(res); assertNull(res.getOwlClassA()); } @@ -291,7 +341,7 @@ void throwsExceptionWhenObjectPropertyCardinalityRestrictionIsNotMet() throws Ex entityA.setUri(ID_TWO); final URI pkThree = URI.create("http://krizik.felk.cvut.cz/ontologies/jopa/entity3"); axioms.add(new AxiomImpl<>(NamedResource.create(ID), getClassDObjectPropertyAssertion(), - new Value<>(NamedResource.create(pkThree)))); + new Value<>(NamedResource.create(pkThree)))); final OWLClassA entityThree = new OWLClassA(); entityThree.setUri(pkThree); final Descriptor fieldDescriptor = new EntityDescriptor(); @@ -300,7 +350,8 @@ void throwsExceptionWhenObjectPropertyCardinalityRestrictionIsNotMet() throws Ex when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, pkThree, fieldDescriptor)) .thenReturn(entityThree); assertThrows(CardinalityConstraintViolatedException.class, - () -> constructor.reconstructEntity(ID, mocks.forOwlClassD().entityType(), descriptor, axioms)); + () -> constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassD() + .entityType(), descriptor), axioms)); } @Test @@ -311,8 +362,8 @@ void testSetFieldValue_DataProperty() throws Exception { entityA.setUri(ID); assertNull(entityA.getStringAttribute()); constructor.setFieldValue(entityA, mocks.forOwlClassA().stringAttribute(), axioms, - mocks.forOwlClassA().entityType(), - descriptor); + mocks.forOwlClassA().entityType(), + descriptor); assertNotNull(entityA.getStringAttribute()); assertEquals(STRING_ATT, entityA.getStringAttribute()); } @@ -332,8 +383,8 @@ void testSetFieldValue_ObjectProperty() throws Exception { when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, fieldDesc)) .thenReturn(entityA); constructor.setFieldValue(entityD, mocks.forOwlClassD().owlClassAAtt(), axioms, - mocks.forOwlClassD().entityType(), - descriptor); + mocks.forOwlClassD().entityType(), + descriptor); assertNotNull(entityD.getOwlClassA()); assertEquals(ID_TWO, entityD.getOwlClassA().getUri()); verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, ID_TWO, fieldDesc); @@ -348,7 +399,7 @@ void testSetFieldValue_Types() { entityA.setUri(ID); assertNull(entityA.getTypes()); constructor.setFieldValue(entityA, mocks.forOwlClassA().typesSpec(), axioms, mocks.forOwlClassA().entityType(), - descriptor); + descriptor); assertNotNull(entityA.getTypes()); assertEquals(TYPES.size(), entityA.getTypes().size()); assertTrue(entityA.getTypes().containsAll(TYPES)); @@ -362,8 +413,8 @@ void testSetFieldValue_Properties() { entityB.setUri(ID); assertNull(entityB.getProperties()); constructor.setFieldValue(entityB, mocks.forOwlClassB().propertiesSpec(), axioms, - mocks.forOwlClassB().entityType(), - descriptor); + mocks.forOwlClassB().entityType(), + descriptor); assertNotNull(entityB.getProperties()); assertEquals(props, entityB.getProperties()); } @@ -374,7 +425,7 @@ private Set> initAxiomsForProperties(Map> props) { final URI property = URI.create(e.getKey()); axioms.addAll(e.getValue().stream().map(val -> new AxiomImpl<>(ID_RESOURCE, Assertion .createPropertyAssertion(property, false), new Value<>(URI.create(val)))) - .collect(Collectors.toList())); + .toList()); } return axioms; } @@ -389,8 +440,8 @@ void testSetFieldValue_ObjectPropertySet() throws Exception { entityJ.setUri(ID); assertNull(entityJ.getOwlClassA()); constructor.setFieldValue(entityJ, mocks.forOwlClassJ().setAttribute(), axioms, - mocks.forOwlClassJ().entityType(), - descriptor); + mocks.forOwlClassJ().entityType(), + descriptor); assertNotNull(entityJ.getOwlClassA()); assertEquals(set.size(), entityJ.getOwlClassA().size()); assertTrue(areEqual(set, entityJ.getOwlClassA())); @@ -415,9 +466,9 @@ private Collection> initAxiomsForReferencedSet(Set as) throw .getAnnotation(OWLObjectProperty.class).iri()); for (OWLClassA a : as) { final Axiom ax = new AxiomImpl<>(NamedResource.create(ID), - Assertion.createObjectPropertyAssertion(property, false), - new Value<>(NamedResource.create( - a.getUri()))); + Assertion.createObjectPropertyAssertion(property, false), + new Value<>(NamedResource.create( + a.getUri()))); axioms.add(ax); } return axioms; @@ -451,7 +502,8 @@ void reconstructsEntityWithDataPropertiesOfBasicTypesAndStringIdentifier() throw final Date date = new Date(); axioms.addAll(createAxiomsForValues(true, i, lng, d, date, null)); - final OWLClassM res = constructor.reconstructEntity(ID, mocks.forOwlClassM().entityType(), descriptor, axioms); + final OWLClassM res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassM() + .entityType(), descriptor), axioms); assertEquals(true, res.getBooleanAttribute()); assertEquals(i, res.getIntAttribute()); assertEquals(lng, res.getLongAttribute()); @@ -467,12 +519,12 @@ private Collection> createAxiomsForValues(Boolean b, Integer i, Long ln final String boolAttIri = OWLClassM.getBooleanAttributeField().getAnnotation(OWLDataProperty.class).iri(); axioms.add( new AxiomImpl<>(ID_RESOURCE, Assertion.createDataPropertyAssertion(URI.create(boolAttIri), false), - new Value<>(b))); + new Value<>(b))); } if (i != null) { final String intAttIri = OWLClassM.getIntAttributeField().getAnnotation(OWLDataProperty.class).iri(); axioms.add(new AxiomImpl<>(ID_RESOURCE, Assertion.createDataPropertyAssertion(URI.create(intAttIri), false), - new Value<>(i))); + new Value<>(i))); } if (lng != null) { final String longAttIri = OWLClassM.getLongAttributeField().getAnnotation(OWLDataProperty.class).iri(); @@ -483,19 +535,19 @@ private Collection> createAxiomsForValues(Boolean b, Integer i, Long ln final String doubleAttIri = OWLClassM.getDoubleAttributeField().getAnnotation(OWLDataProperty.class).iri(); axioms.add( new AxiomImpl<>(ID_RESOURCE, Assertion.createDataPropertyAssertion(URI.create(doubleAttIri), false), - new Value<>(d))); + new Value<>(d))); } if (date != null) { final String dateAttIri = OWLClassM.getDateAttributeField().getAnnotation(OWLDataProperty.class).iri(); axioms.add( new AxiomImpl<>(ID_RESOURCE, Assertion.createDataPropertyAssertion(URI.create(dateAttIri), false), - new Value<>(date))); + new Value<>(date))); } if (severity != null) { final String enumAttIri = OWLClassM.getEnumAttributeField().getAnnotation(OWLDataProperty.class).iri(); axioms.add( new AxiomImpl<>(ID_RESOURCE, Assertion.createDataPropertyAssertion(URI.create(enumAttIri), false), - new Value<>(severity.toString()))); + new Value<>(severity.toString()))); } return axioms; } @@ -508,7 +560,7 @@ void reconstructsEntityWithEnumDataProperty() throws Exception { axioms.addAll(createAxiomsForValues(null, null, null, null, null, enumValue)); final OWLClassM result = constructor - .reconstructEntity(ID, mocks.forOwlClassM().entityType(), descriptor, axioms); + .reconstructEntity(constructionConfig(ID, mocks.forOwlClassM().entityType(), descriptor), axioms); assertNotNull(result); assertNotNull(result.getEnumAttribute()); assertEquals(enumValue, result.getEnumAttribute()); @@ -520,7 +572,8 @@ void icsAreValidatedForAllFieldsOnEntityLoad() { axioms.add(getClassAssertionAxiomForType(ID, OWLClassL.getClassIri())); assertThrows(IntegrityConstraintViolatedException.class, - () -> constructor.reconstructEntity(ID, mocks.forOwlClassL().entityType(), descriptor, axioms)); + () -> constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassL() + .entityType(), descriptor), axioms)); } @Test @@ -534,7 +587,7 @@ void icValidationIsSkippedOnLoadBasedOnProperties() { when(mapperMock.getConfiguration()).thenReturn(conf); final OWLClassL result = constructor - .reconstructEntity(ID, mocks.forOwlClassL().entityType(), descriptor, axioms); + .reconstructEntity(constructionConfig(ID, mocks.forOwlClassL().entityType(), descriptor), axioms); assertNotNull(result); assertNull(result.getSingleA()); } @@ -546,7 +599,7 @@ void icsAreValidatedOnLazilyLoadedFieldFetch() throws Exception { final OWLClassL instance = new OWLClassL(); assertThrows(IntegrityConstraintViolatedException.class, () -> constructor .setFieldValue(instance, mocks.forOwlClassL().referencedListAtt(), fieldAxiom, - mocks.forOwlClassL().entityType(), descriptor)); + mocks.forOwlClassL().entityType(), descriptor)); } private Set> initInvalidFieldValuesForICValidation() throws NoSuchFieldException { @@ -581,7 +634,7 @@ void icsAreNotValidatedOnLazilyLoadedFetchWhenLoadingICValidationIsDisabled() th final OWLClassL instance = new OWLClassL(); constructor.setFieldValue(instance, mocks.forOwlClassL().referencedListAtt(), fieldAxiom, - mocks.forOwlClassL().entityType(), descriptor); + mocks.forOwlClassL().entityType(), descriptor); } @Test @@ -592,11 +645,13 @@ void instanceReconstructionSkipsAxiomsForWhichNoAttributeCanBeFound() throws Exc final Assertion unknown = Assertion.createAnnotationPropertyAssertion(Generators.createPropertyIdentifier(), false); axioms.add(new AxiomImpl<>(ID_RESOURCE, unknown, new Value<>("UnknownPropertyValue"))); - final OWLClassA res = constructor.reconstructEntity(ID, mocks.forOwlClassA().entityType(), descriptor, axioms); + final OWLClassA res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassA() + .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(ID, res.getUri()); assertNotNull(res.getStringAttribute()); - assertNull(res.getTypes()); + assertNotNull(res.getTypes()); + assertTrue(res.getTypes().isEmpty()); } @Test @@ -607,16 +662,17 @@ void reconstructsInstanceWithMappedSuperclass() throws Exception { axioms.add(getStringAttAssertionAxiom(ID, STRING_ATT, OWLClassQ.getParentStringField())); final URI labelUri = URI.create(OWLClassQ.getLabelField().getAnnotation(OWLAnnotationProperty.class).iri()); axioms.add(new AxiomImpl<>(ID_RESOURCE, Assertion.createAnnotationPropertyAssertion(labelUri, false), - new Value<>(STRING_ATT))); + new Value<>(STRING_ATT))); final URI owlClassAUri = URI.create(OWLClassQ.getOwlClassAField().getAnnotation(OWLObjectProperty.class).iri()); axioms.add(new AxiomImpl<>(ID_RESOURCE, Assertion.createObjectPropertyAssertion(owlClassAUri, false), - new Value<>(NamedResource.create(ID_TWO)))); + 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))) .thenReturn(a); - final OWLClassQ res = constructor.reconstructEntity(ID, mocks.forOwlClassQ().entityType(), descriptor, axioms); + final OWLClassQ res = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassQ() + .entityType(), descriptor), axioms); assertNotNull(res); assertEquals(ID, res.getUri()); assertEquals(STRING_ATT, res.getStringAttribute()); @@ -634,19 +690,21 @@ void reconstructsInstanceWithPluralAnnotationProperty() { axioms.add(new AxiomImpl<>(ID_RESOURCE, assertion, new Value<>(STRING_ATT))); final OWLClassN result = - constructor.reconstructEntity(ID, mocks.forOwlClassN().entityType(), descriptor, axioms); + constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassN() + .entityType(), descriptor), axioms); assertNotNull(result); assertEquals(ID.toString(), result.getId()); assertEquals(Collections.singleton(STRING_ATT), result.getPluralAnnotation()); } @Test - void setFieldValueSetsNothingWhenAxiomsAreEmpty() { + void setFieldValueSetsEmptyCollectionWhenAxiomsAreEmpty() { final OWLClassA entity = new OWLClassA(Generators.createIndividualIdentifier()); assertNull(entity.getTypes()); constructor.setFieldValue(entity, mocks.forOwlClassA().typesSpec(), Collections.emptyList(), - mocks.forOwlClassA().entityType(), descriptor); - assertNull(entity.getTypes()); + mocks.forOwlClassA().entityType(), descriptor); + assertNotNull(entity.getTypes()); + assertTrue(entity.getTypes().isEmpty()); } @Test @@ -654,6 +712,102 @@ void setFieldValueValidatesIntegrityConstraintsAlsoForEmptyAxiomCollection() { final OWLClassL instance = new OWLClassL(Generators.createIndividualIdentifier()); assertThrows(IntegrityConstraintViolatedException.class, () -> constructor .setFieldValue(instance, mocks.forOwlClassL().owlClassAAtt(), Collections.emptyList(), - mocks.forOwlClassL().entityType(), descriptor)); + mocks.forOwlClassL().entityType(), descriptor)); + } + + @Test + void reconstructEntitySetsEagerlyLoadedPluralAttributeValuesToEmptyCollectionWhenNoAxiomsWereLoadedForThem() { + final List> axioms = List.of( + getClassAssertionAxiomForType(ID, OWLClassC.getClassIri()) + ); + final OWLClassC result = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassC() + .entityType(), descriptor), axioms); + assertNotNull(result); + assertNotNull(result.getReferencedList()); + assertTrue(result.getReferencedList().isEmpty()); + } + + @Test + void reconstructEntityRegistersLoadStateDescriptorForReconstructedInstanceInLoadStateRegistry() { + final List> axioms = List.of( + getClassAssertionAxiomForType(ID, OWLClassA.getClassIri()) + ); + final OWLClassA result = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassA() + .entityType(), descriptor), axioms); + assertTrue(loadStateRegistry.contains(result)); + } + + @Test + void reconstructEntityMarksLazilyLoadedAttributesAsNotLoadedWhenThereExistAxiomsForThem() { + final List> axioms = List.of( + getClassAssertionAxiomForType(ID, OWLClassC.getClassIri()), + new AxiomImpl(NamedResource.create(ID), Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_SIMPLE_LIST), false), new Value<>(NamedResource.create(Generators.createIndividualIdentifier()))) + ); + final OWLClassC result = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassC() + .entityType(), descriptor), axioms); + final LoadStateDescriptor loadStates = loadStateRegistry.get(result); + assertNotNull(loadStates); + assertEquals(LoadState.LOADED, loadStates.isLoaded(mocks.forOwlClassC().referencedListAtt())); + assertEquals(LoadState.NOT_LOADED, loadStates.isLoaded(mocks.forOwlClassC().simpleListAtt())); + } + + @Test + void reconstructEntityMarksLazilyLoadedPluralAttributesAsLoadedWhenThereExistNoAxiomsForThem() { + final List> axioms = List.of( + getClassAssertionAxiomForType(ID, OWLClassC.getClassIri()) + ); + final OWLClassC result = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassC() + .entityType(), descriptor), axioms); + final LoadStateDescriptor loadStates = loadStateRegistry.get(result); + assertNotNull(loadStates); + assertEquals(LoadState.LOADED, loadStates.isLoaded(mocks.forOwlClassC().referencedListAtt())); + assertEquals(LoadState.LOADED, loadStates.isLoaded(mocks.forOwlClassC().simpleListAtt())); + } + + @Test + void reconstructEntityMarksSingularLazilyLoadedAttributeAsLoadedWhenThereExistNoAxiomsForThem() { + when(mocks.forOwlClassD().owlClassAAtt().getFetchType()).thenReturn(FetchType.LAZY); + final List> axioms = List.of( + getClassAssertionAxiomForType(ID, OWLClassD.getClassIri()) + ); + final OWLClassD result = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassD() + .entityType(), descriptor), axioms); + final LoadStateDescriptor loadStates = loadStateRegistry.get(result); + assertNotNull(loadStates); + assertEquals(LoadState.LOADED, loadStates.isLoaded(mocks.forOwlClassD().owlClassAAtt())); + } + + @Test + void reconstructEntityMarksSingularLazilyLoadedAttributeAsNotLoadedWhenThereExistAxiomsForThem() { + 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 OWLClassD result = constructor.reconstructEntity(constructionConfig(ID, mocks.forOwlClassD() + .entityType(), descriptor), axioms); + final LoadStateDescriptor loadStates = loadStateRegistry.get(result); + assertNotNull(loadStates); + assertEquals(LoadState.NOT_LOADED, loadStates.isLoaded(mocks.forOwlClassD().owlClassAAtt())); + } + + @Test + void reconstructEntityLoadsLazyReferencesWhenForceEagerIsSpecifiedForReconstruction() { + when(mocks.forOwlClassD().owlClassAAtt().getFetchType()).thenReturn(FetchType.LAZY); + final OWLClassA aInstance = Generators.generateOwlClassAInstance(); + final List> axioms = List.of( + 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); + final OWLClassD result = constructor.reconstructEntity( + new EntityConstructor.EntityConstructionParameters<>(ID, mocks.forOwlClassD() + .entityType(), descriptor, true), axioms); + assertNotNull(result); + assertNotNull(result.getOwlClassA()); + assertEquals(aInstance, result.getOwlClassA()); + final LoadStateDescriptor loadStates = loadStateRegistry.get(result); + assertNotNull(loadStates); + assertEquals(LoadState.LOADED, loadStates.isLoaded(mocks.forOwlClassD().owlClassAAtt())); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/InstanceLoaderTestBase.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/InstanceLoaderTestBase.java index c5f4b8991..7e66ad460 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/InstanceLoaderTestBase.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/InstanceLoaderTestBase.java @@ -21,7 +21,7 @@ import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.sessions.CacheManager; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; import cz.cvut.kbss.ontodriver.Connection; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.model.NamedResource; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ListDescriptorFactoryTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ListDescriptorFactoryTest.java new file mode 100644 index 000000000..430e20da7 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ListDescriptorFactoryTest.java @@ -0,0 +1,82 @@ +package cz.cvut.kbss.jopa.oom; + +import cz.cvut.kbss.jopa.environment.Vocabulary; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.model.IRI; +import cz.cvut.kbss.jopa.model.metamodel.ListAttribute; +import cz.cvut.kbss.jopa.vocabulary.RDF; +import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.ReferencedListValueDescriptor; +import cz.cvut.kbss.ontodriver.model.NamedResource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ListDescriptorFactoryTest { + + private static final String HAS_LIST = Vocabulary.ATTRIBUTE_BASE + "hasList"; + + private final NamedResource owner = NamedResource.create(Generators.createIndividualIdentifier()); + + @Test + void createReferencedListDescriptorCreatesDescriptorTerminatedByNilForRDFCollectionListAttribute() { + final ListAttribute att = mock(ListAttribute.class); + when(att.isRDFCollection()).thenReturn(true); + when(att.getHasNextPropertyIRI()).thenReturn(IRI.create(RDF.REST)); + when(att.getHasContentsPropertyIRI()).thenReturn(IRI.create(RDF.FIRST)); + when(att.getIRI()).thenReturn(IRI.create(HAS_LIST)); + + final ReferencedListDescriptor result = ListDescriptorFactory.createReferencedListDescriptor(owner, att); + checkReferencedListDescriptorProperties(result); + assertTrue(result.isTerminatedByNil()); + } + + private static void checkReferencedListDescriptorProperties(ReferencedListDescriptor desc) { + assertEquals(HAS_LIST, desc.getListProperty().getIdentifier().toString()); + assertEquals(RDF.FIRST, desc.getNodeContent().getIdentifier().toString()); + assertEquals(RDF.REST, desc.getNextNode().getIdentifier().toString()); + } + + @Test + void createReferencedListDescriptorCreatesDescriptorNotTerminatedByNilForReferencedListAttribute() { + final ListAttribute att = mock(ListAttribute.class); + when(att.isRDFCollection()).thenReturn(false); + when(att.getHasNextPropertyIRI()).thenReturn(IRI.create(RDF.REST)); + when(att.getHasContentsPropertyIRI()).thenReturn(IRI.create(RDF.FIRST)); + when(att.getIRI()).thenReturn(IRI.create(HAS_LIST)); + + final ReferencedListDescriptor result = ListDescriptorFactory.createReferencedListDescriptor(owner, att); + checkReferencedListDescriptorProperties(result); + assertFalse(result.isTerminatedByNil()); + } + + @Test + void createReferencedListValueDescriptorCreatesDescriptorTerminatedByNilForRDFCollectionListAttribute() { + final ListAttribute att = mock(ListAttribute.class); + when(att.isRDFCollection()).thenReturn(true); + when(att.getHasNextPropertyIRI()).thenReturn(IRI.create(RDF.REST)); + when(att.getHasContentsPropertyIRI()).thenReturn(IRI.create(RDF.FIRST)); + when(att.getIRI()).thenReturn(IRI.create(HAS_LIST)); + + final ReferencedListValueDescriptor result = ListDescriptorFactory.createReferencedListValueDescriptor(owner, att); + checkReferencedListDescriptorProperties(result); + assertTrue(result.isTerminatedByNil()); + } + + @Test + void createReferencedListValueDescriptorCreatesDescriptorNotTerminatedByNilForReferencedListAttribute() { + final ListAttribute att = mock(ListAttribute.class); + when(att.isRDFCollection()).thenReturn(false); + when(att.getHasNextPropertyIRI()).thenReturn(IRI.create(RDF.REST)); + when(att.getHasContentsPropertyIRI()).thenReturn(IRI.create(RDF.FIRST)); + when(att.getIRI()).thenReturn(IRI.create(HAS_LIST)); + + final ReferencedListValueDescriptor result = ListDescriptorFactory.createReferencedListValueDescriptor(owner, att); + checkReferencedListDescriptorProperties(result); + assertFalse(result.isTerminatedByNil()); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ListPropertyStrategyTestBase.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ListPropertyStrategyTestBase.java index 0e14af671..2be6b9bf8 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ListPropertyStrategyTestBase.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ListPropertyStrategyTestBase.java @@ -104,9 +104,9 @@ static ListAttributeImpl initEnumListAttribute() throws when(att.getConverter()).thenReturn(new ObjectOneOfEnumConverter<>(OneOfEnum.class)); when(att.getCollectionType()).thenReturn(CollectionType.LIST); when(att.getJavaField()).thenReturn(WithEnumList.class.getDeclaredField("enumList")); - when(att.getOWLObjectPropertyHasNextIRI()).thenReturn(IRI.create(SequencesVocabulary.s_p_hasNext)); + when(att.getHasNextPropertyIRI()).thenReturn(IRI.create(SequencesVocabulary.s_p_hasNext)); // For referenced list only - when(att.getOWLPropertyHasContentsIRI()).thenReturn(IRI.create(SequencesVocabulary.s_p_hasContents)); + when(att.getHasContentsPropertyIRI()).thenReturn(IRI.create(SequencesVocabulary.s_p_hasContents)); return att; } 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 a7d895da8..d0c9f8d5e 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 @@ -17,46 +17,94 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.environment.*; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassB; +import cz.cvut.kbss.jopa.environment.OWLClassC; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.OWLClassR; +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.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.environment.utils.TestEnvironmentUtils; import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; +import cz.cvut.kbss.jopa.model.LoadState; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.SequencesVocabulary; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.oom.exceptions.UnpersistedChangeException; -import cz.cvut.kbss.jopa.sessions.CacheManager; -import cz.cvut.kbss.jopa.sessions.LoadingParameters; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.oom.exception.UnpersistedChangeException; +import cz.cvut.kbss.jopa.sessions.AbstractUnitOfWork; +import cz.cvut.kbss.jopa.sessions.cache.Descriptors; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; import cz.cvut.kbss.ontodriver.Connection; import cz.cvut.kbss.ontodriver.Lists; import cz.cvut.kbss.ontodriver.Types; -import cz.cvut.kbss.ontodriver.descriptor.*; +import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.AxiomValueDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.ReferencedListValueDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptor; +import cz.cvut.kbss.ontodriver.descriptor.SimpleListValueDescriptor; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; -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.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.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import java.lang.reflect.Field; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anySet; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) class ObjectOntologyMapperTest { private static final URI IDENTIFIER = Generators.createIndividualIdentifier(); @@ -67,7 +115,7 @@ class ObjectOntologyMapperTest { private static AxiomDescriptor axiomDescriptor; @Mock - private UnitOfWorkImpl uowMock; + private AbstractUnitOfWork uowMock; @Mock private Connection connectionMock; @@ -85,6 +133,8 @@ class ObjectOntologyMapperTest { @Mock private EntityDeconstructor entityDeconstructorMock; + private final LoadStateDescriptorRegistry loadStateRegistry = new LoadStateDescriptorRegistry(Object::toString); + private MetamodelMocks mocks; private EntityType etAMock; private LoadingParameters loadingParameters; @@ -93,8 +143,7 @@ class ObjectOntologyMapperTest { @BeforeAll static void setUpBeforeClass() { - entityA = new OWLClassA(); - entityA.setUri(IDENTIFIER); + entityA = new OWLClassA(IDENTIFIER); entityA.setStringAttribute("SomeStringAttribute"); aTypes = new HashSet<>(); aTypes.add("http://krizik.felk.cvut.cz/ontologies/entityU"); @@ -105,40 +154,40 @@ static void setUpBeforeClass() { @BeforeEach void setUp() throws Exception { - MockitoAnnotations.openMocks(this); when(uowMock.getMetamodel()).thenReturn(metamodelMock); when(uowMock.getLiveObjectCache()).thenReturn(cacheMock); when(uowMock.getConfiguration()).thenReturn(new Configuration(Collections.emptyMap())); + when(uowMock.getLoadStateRegistry()).thenReturn(loadStateRegistry); this.loadingParameters = new LoadingParameters<>(OWLClassA.class, IDENTIFIER, aDescriptor); this.mocks = new MetamodelMocks(); mocks.setMocks(metamodelMock); this.etAMock = mocks.forOwlClassA().entityType(); when(descriptorFactoryMock.createForEntityLoading(loadingParameters, etAMock)).thenReturn(axiomDescriptor); when(descriptorFactoryMock.createForFieldLoading(IDENTIFIER, mocks.forOwlClassA().typesSpec(), - aDescriptor, mocks.forOwlClassA().entityType())).thenReturn( + aDescriptor, mocks.forOwlClassA().entityType())).thenReturn( axiomDescriptor); entityA.setTypes(null); this.mapper = new ObjectOntologyMapperImpl(uowMock, connectionMock); TestEnvironmentUtils.setMock(mapper, - ObjectOntologyMapperImpl.class.getDeclaredField("descriptorFactory"), - descriptorFactoryMock); + ObjectOntologyMapperImpl.class.getDeclaredField("descriptorFactory"), + descriptorFactoryMock); TestEnvironmentUtils.setMock(mapper, - ObjectOntologyMapperImpl.class.getDeclaredField("entityBuilder"), - entityConstructorMock); + ObjectOntologyMapperImpl.class.getDeclaredField("entityBuilder"), + entityConstructorMock); TestEnvironmentUtils.setMock(mapper, - ObjectOntologyMapperImpl.class.getDeclaredField("entityBreaker"), - entityDeconstructorMock); + ObjectOntologyMapperImpl.class.getDeclaredField("entityBreaker"), + entityDeconstructorMock); } private Collection> getAxiomsForEntityA() { final List> res = new ArrayList<>(); final NamedResource identifier = NamedResource.create(IDENTIFIER); res.add(new AxiomImpl<>(identifier, Assertion.createClassAssertion(false), - new Value(NamedResource.create(Vocabulary.c_OwlClassA)))); + new Value(NamedResource.create(Vocabulary.c_OwlClassA)))); res.add(new AxiomImpl<>(identifier, - Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_a_stringAttribute), - false), - new Value<>("stringAttribute"))); + Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_a_stringAttribute), + false), + new Value<>("stringAttribute"))); return res; } @@ -154,12 +203,14 @@ void testLoadFieldValue() throws Exception { final FieldSpecification types = (FieldSpecification) invocation.getArguments()[1]; EntityPropertiesUtils.setFieldValue(types.getJavaField(), a, aTypes); return null; - }).when(entityConstructorMock).setFieldValue(entityA, mocks.forOwlClassA().typesSpec(), axiomsForA, etAMock, aDescriptor); + }).when(entityConstructorMock) + .setFieldValue(entityA, mocks.forOwlClassA().typesSpec(), axiomsForA, etAMock, aDescriptor); mapper.loadFieldValue(entityA, mocks.forOwlClassA().typesSpec(), aDescriptor); assertNotNull(typesField.get(entityA)); assertEquals(aTypes, entityA.getTypes()); verify(connectionMock).find(axiomDescriptor); - verify(entityConstructorMock).setFieldValue(entityA, mocks.forOwlClassA().typesSpec(), axiomsForA, etAMock, aDescriptor); + verify(entityConstructorMock).setFieldValue(entityA, mocks.forOwlClassA() + .typesSpec(), axiomsForA, etAMock, aDescriptor); } @Test @@ -168,10 +219,11 @@ void testLoadFieldValueStorageException() throws Exception { final Field typesField = OWLClassA.getTypesField(); typesField.setAccessible(true); assertNull(typesField.get(entityA)); - assertThrows(StorageAccessException.class, () -> mapper.loadFieldValue(entityA, mocks.forOwlClassA().typesSpec(), aDescriptor)); + assertThrows(StorageAccessException.class, () -> mapper.loadFieldValue(entityA, mocks.forOwlClassA() + .typesSpec(), aDescriptor)); verify(entityConstructorMock, never()).setFieldValue(any(), - eq(mocks.forOwlClassA().typesSpec()), any(), any(), - any()); + eq(mocks.forOwlClassA().typesSpec()), any(), any(), + any()); } @Test @@ -183,26 +235,13 @@ void testPersistEntity() { verify(madMock).persist(connectionMock); } - @Test - void testPersistEntityWithGeneratedURI() throws Exception { - final OWLClassA a = new OWLClassA(); - final AxiomValueGatherer madMock = mock(AxiomValueGatherer.class); - final URI generatedUri = URI.create("http://generatedUri" + System.currentTimeMillis()); - when(entityDeconstructorMock.mapEntityToAxioms(generatedUri, a, etAMock, aDescriptor)) - .thenReturn(madMock); - when(connectionMock.generateIdentifier(etAMock.getIRI().toURI())).thenReturn(generatedUri); - - assertNull(a.getUri()); - mapper.persistEntity(null, a, aDescriptor); - assertNotNull(a.getUri()); - verify(connectionMock).generateIdentifier(etAMock.getIRI().toURI()); - verify(madMock).persist(connectionMock); - } - @Test void testGetEntityFromCacheOrOntologyFromCache() { when(cacheMock.contains(OWLClassA.class, IDENTIFIER, aDescriptor)).thenReturn(Boolean.TRUE); when(cacheMock.get(OWLClassA.class, IDENTIFIER, aDescriptor)).thenReturn(entityA); + 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); assertNotNull(res); assertSame(entityA, res); @@ -214,7 +253,7 @@ 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); + aDescriptor); assertNotNull(res); assertSame(entityA, res); } @@ -228,6 +267,9 @@ void getEntityFromCacheOrOntologyLoadsEntityWhenItIsNotInCache() throws Exceptio loader = spy(loader); instanceLoaderField.set(mapper, loader); doReturn(entityA).when(loader).loadEntity(loadingParameters); + final LoadStateDescriptor loadStateDescriptor = new LoadStateDescriptor<>(entityA, mocks.forOwlClassA() + .entityType(), LoadState.UNKNOWN); + loadStateRegistry.put(entityA, loadStateDescriptor); final OWLClassA res = mapper.getEntityFromCacheOrOntology(OWLClassA.class, IDENTIFIER, aDescriptor); assertSame(entityA, res); verify(loader).loadEntity(loadingParameters); @@ -262,20 +304,20 @@ void throwsStorageAccessWhenRemovingEntity() throws Exception { void updatesFieldValueInTheOntology() { final AxiomValueGatherer axiomBuilderMock = mock(AxiomValueGatherer.class); when(entityDeconstructorMock.mapFieldToAxioms(IDENTIFIER, entityA, - mocks.forOwlClassA().stringAttribute(), etAMock, - aDescriptor)).thenReturn( + mocks.forOwlClassA().stringAttribute(), etAMock, + aDescriptor)).thenReturn( axiomBuilderMock); mapper.updateFieldValue(entityA, mocks.forOwlClassA().stringAttribute(), aDescriptor); verify(entityDeconstructorMock).mapFieldToAxioms(IDENTIFIER, entityA, - mocks.forOwlClassA().stringAttribute(), etAMock, aDescriptor); + mocks.forOwlClassA().stringAttribute(), etAMock, aDescriptor); verify(axiomBuilderMock).update(connectionMock); } @Test void removeEntityCreatesDescriptorForRemovalOfAllEntityAttributes() { when(descriptorFactoryMock - .createForEntityLoading(new LoadingParameters<>(OWLClassA.class, IDENTIFIER, aDescriptor, true), - etAMock)).thenReturn(axiomDescriptor); + .createForEntityLoading(new LoadingParameters<>(OWLClassA.class, IDENTIFIER, aDescriptor, true), + etAMock)).thenReturn(axiomDescriptor); mapper.removeEntity(IDENTIFIER, OWLClassA.class, aDescriptor); final ArgumentCaptor> captor = ArgumentCaptor.forClass(LoadingParameters.class); verify(descriptorFactoryMock).createForEntityLoading(captor.capture(), eq(etAMock)); @@ -289,12 +331,12 @@ void containsEntityThrowsStorageAccessExceptionWhenOntoDriverExceptionIsThrown() when(connectionMock.contains(any(Axiom.class), anySet())).thenThrow(new OntoDriverException(message)); final StorageAccessException ex = assertThrows(StorageAccessException.class, - () -> mapper.containsEntity(OWLClassA.class, IDENTIFIER, - aDescriptor)); + () -> mapper.containsEntity(OWLClassA.class, IDENTIFIER, + aDescriptor)); assertThat(ex.getMessage(), containsString(message)); verify(connectionMock).contains( new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createClassAssertion(false), - new Value<>(NamedResource.create(OWLClassA.getClassIri()))), Collections.emptySet()); + new Value<>(NamedResource.create(OWLClassA.getClassIri()))), Collections.emptySet()); } @Test @@ -306,7 +348,7 @@ void loadSimpleListThrowsStorageAccessExceptionWhenOntoDriverExceptionIsThrown() final SimpleListDescriptor listDescriptorMock = mock(SimpleListDescriptor.class); final StorageAccessException ex = assertThrows(StorageAccessException.class, - () -> mapper.loadSimpleList(listDescriptorMock)); + () -> mapper.loadSimpleList(listDescriptorMock)); assertThat(ex.getMessage(), containsString(message)); verify(listsMock).loadSimpleList(listDescriptorMock); } @@ -321,7 +363,7 @@ void loadReferencedListThrowsStorageAccessExceptionWhenOntoDriverExceptionIsThro final ReferencedListDescriptor listDescriptorMock = mock(ReferencedListDescriptor.class); final StorageAccessException ex = assertThrows(StorageAccessException.class, - () -> mapper.loadReferencedList(listDescriptorMock)); + () -> mapper.loadReferencedList(listDescriptorMock)); assertThat(ex.getMessage(), containsString(message)); verify(listsMock).loadReferencedList(listDescriptorMock); } @@ -335,8 +377,11 @@ void usesTwoStepInstanceLoaderForLoadingInstanceOfEntityWithSubtypes() throws Ex twoStepLoaderField.set(mapper, twoStepLoader); final OWLClassS entity = new OWLClassR(); final LoadingParameters loadingParameters = new LoadingParameters<>(OWLClassS.class, IDENTIFIER, - aDescriptor); + aDescriptor); doReturn(entity).when(twoStepLoader).loadEntity(loadingParameters); + final LoadStateDescriptor loadStateDescriptor = new LoadStateDescriptor<>(entity, mocks.forOwlClassS() + .entityType(), LoadState.UNKNOWN); + loadStateRegistry.put(entity, loadStateDescriptor); final OWLClassS result = mapper.loadEntity(loadingParameters); assertSame(entity, result); @@ -347,6 +392,9 @@ void usesTwoStepInstanceLoaderForLoadingInstanceOfEntityWithSubtypes() throws Ex void loadEntityLoadsInstanceFromCacheWhenItIsPresentThere() throws Exception { when(cacheMock.contains(OWLClassA.class, IDENTIFIER, loadingParameters.getDescriptor())).thenReturn(true); when(cacheMock.get(OWLClassA.class, IDENTIFIER, loadingParameters.getDescriptor())).thenReturn(entityA); + final LoadStateDescriptor loadStateDescriptor = new LoadStateDescriptor<>(entityA, mocks.forOwlClassA() + .entityType(), LoadState.UNKNOWN); + doReturn(loadStateDescriptor).when(cacheMock).getLoadStateDescriptor(entityA); final OWLClassA result = mapper.loadEntity(loadingParameters); assertSame(entityA, result); @@ -360,6 +408,9 @@ void loadEntityDeterminesConcreteEntityTypeAndLoadsItFromCacheWhenItIsPresentThe entity.setUri(IDENTIFIER); when(cacheMock.contains(OWLClassR.class, IDENTIFIER, aDescriptor)).thenReturn(true); when(cacheMock.get(OWLClassR.class, IDENTIFIER, aDescriptor)).thenReturn(entity); + final LoadStateDescriptor loadStateDescriptor = new LoadStateDescriptor<>(entity, mocks.forOwlClassR() + .entityType(), LoadState.UNKNOWN); + doReturn(loadStateDescriptor).when(cacheMock).getLoadStateDescriptor(entity); final Types typesMock = mock(Types.class); final NamedResource individual = NamedResource.create(IDENTIFIER); final URI typeUri = URI.create(Vocabulary.C_OWLClassR); @@ -369,7 +420,7 @@ void loadEntityDeterminesConcreteEntityTypeAndLoadsItFromCacheWhenItIsPresentThe when(connectionMock.types()).thenReturn(typesMock); final LoadingParameters loadingParameters = new LoadingParameters<>(OWLClassS.class, IDENTIFIER, - aDescriptor); + aDescriptor); final OWLClassS result = mapper.loadEntity(loadingParameters); assertSame(entity, result); verify(cacheMock).get(OWLClassR.class, IDENTIFIER, aDescriptor); @@ -382,7 +433,7 @@ void loadEntityPutsItIntoSecondLevelCache() throws Exception { when(connectionMock.find(any(AxiomDescriptor.class))).thenReturn(axiomsForA); final OWLClassA result = mapper.loadEntity(loadingParameters); assertNotNull(result); - verify(cacheMock).add(IDENTIFIER, result, loadingParameters.getDescriptor()); + verify(cacheMock).add(IDENTIFIER, result, new Descriptors(loadingParameters.getDescriptor(), loadStateRegistry.get(result))); } @Test @@ -400,17 +451,17 @@ void loadEntityPutsIntoSecondLevelCacheThenEntityAndEntitiesItReferences() throw }); final OWLClassD result = mapper.loadEntity(new LoadingParameters<>(OWLClassD.class, identifier, aDescriptor)); assertNotNull(result); - verify(cacheMock).add(identifier, result, aDescriptor); - verify(cacheMock).add(IDENTIFIER, result.getOwlClassA(), aDescriptor); + verify(cacheMock).add(identifier, result, new Descriptors(aDescriptor, loadStateRegistry.get(result))); + verify(cacheMock).add(IDENTIFIER, result.getOwlClassA(), new Descriptors(aDescriptor, loadStateRegistry.get(result.getOwlClassA()))); } private Collection> axiomsForD(URI identifier) { final NamedResource id = NamedResource.create(identifier); final Collection> axioms = new ArrayList<>(); axioms.add(new AxiomImpl<>(id, Assertion.createClassAssertion(false), - new Value<>(NamedResource.create(Vocabulary.c_OwlClassD)))); + new Value<>(NamedResource.create(Vocabulary.c_OwlClassD)))); axioms.add(new AxiomImpl<>(id, Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false), - new Value(NamedResource.create(IDENTIFIER)))); + new Value(NamedResource.create(IDENTIFIER)))); return axioms; } @@ -448,16 +499,16 @@ void persistRemovesPendingAssertionsWithTargetBeingPersistedObject() throws Exce private void initDeconstructorMock(OWLClassD d, Descriptor descriptor) { when(entityDeconstructorMock - .mapEntityToAxioms(d.getUri(), d, metamodelMock.entity(OWLClassD.class), descriptor)) + .mapEntityToAxioms(d.getUri(), d, metamodelMock.entity(OWLClassD.class), descriptor)) .then(invocationOnMock -> { final Assertion assertion = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false); final Descriptor attDescriptor = descriptor.getAttributeDescriptor(mocks.forOwlClassD().owlClassAAtt()); mapper.registerPendingAssertion(NamedResource.create(d.getUri()), assertion, d.getOwlClassA(), - attDescriptor.getSingleContext().orElse(null)); + attDescriptor.getSingleContext().orElse(null)); return new AxiomValueGatherer(NamedResource.create(d.getUri()), - descriptor.getSingleContext().orElse(null)); + descriptor.getSingleContext().orElse(null)); }); } @@ -492,7 +543,7 @@ void checkForUnpersistedChangesThrowsPendingPersistExceptionWhenThereArePendingC mapper.checkForUnpersistedChanges(); mapper.registerPendingAssertion(NamedResource.create(subject), assertion, entityA, null); final UnpersistedChangeException ex = assertThrows(UnpersistedChangeException.class, - () -> mapper.checkForUnpersistedChanges()); + () -> mapper.checkForUnpersistedChanges()); assertThat(ex.getMessage(), containsString(entityA.toString())); } @@ -506,8 +557,8 @@ void removeEntityRemovesPendingReferenceWhenOwnerIsRemoved() throws Exception { axiomDescriptor.addAssertion(Assertion.createClassAssertion(false)); axiomDescriptor.addAssertion(Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false)); when(descriptorFactoryMock - .createForEntityLoading(new LoadingParameters<>(OWLClassD.class, IDENTIFIER, descriptor, true), - metamodelMock.entity(OWLClassD.class))).thenReturn(axiomDescriptor); + .createForEntityLoading(new LoadingParameters<>(OWLClassD.class, IDENTIFIER, descriptor, true), + metamodelMock.entity(OWLClassD.class))).thenReturn(axiomDescriptor); final PendingReferenceRegistry registry = getPendingAssertionRegistry(); assertTrue(registry.getPendingResources().contains(entityA)); @@ -532,7 +583,7 @@ void updateFieldValueRemovesPendingReference() throws Exception { final OWLClassA differentA = new OWLClassA(Generators.createIndividualIdentifier()); owner.setOwlClassA(differentA); when(entityDeconstructorMock.mapFieldToAxioms(IDENTIFIER, owner, mocks.forOwlClassD().owlClassAAtt(), - metamodelMock.entity(OWLClassD.class), descriptor)) + metamodelMock.entity(OWLClassD.class), descriptor)) .thenReturn(new AxiomValueGatherer(NamedResource.create(IDENTIFIER), null)); mapper.updateFieldValue(owner, mocks.forOwlClassD().owlClassAAtt(), descriptor); @@ -591,8 +642,8 @@ void loadReferenceRetrievesEntityReferenceViaDefaultInstanceLoader() throws Exce when(entityConstructorMock.createEntityInstance(IDENTIFIER, mocks.forOwlClassA().entityType())) .thenReturn(new OWLClassA(IDENTIFIER)); final Axiom axiom = new AxiomImpl<>(NamedResource.create(IDENTIFIER), - Assertion.createClassAssertion(false), - new Value<>(NamedResource.create(Vocabulary.c_OwlClassA))); + Assertion.createClassAssertion(false), + new Value<>(NamedResource.create(Vocabulary.c_OwlClassA))); when(connectionMock.contains(axiom, Collections.emptySet())).thenReturn(true); final OWLClassA result = mapper .loadReference(new LoadingParameters<>(OWLClassA.class, IDENTIFIER, aDescriptor)); @@ -609,12 +660,12 @@ void loadReferenceUsesTwoStepLoaderWhenEntityTypeHasSubclasses() throws Exceptio final NamedResource idResource = NamedResource.create(IDENTIFIER); final Set> typesAxioms = Collections.singleton( new AxiomImpl<>(idResource, Assertion.createClassAssertion(false), - new Value<>(URI.create(Vocabulary.C_OWLClassR)))); + new Value<>(URI.create(Vocabulary.C_OWLClassR)))); when(typesMock.getTypes(idResource, Collections.emptySet(), false)).thenReturn(typesAxioms); final OWLClassS result = mapper .loadReference(new LoadingParameters<>(OWLClassS.class, IDENTIFIER, new EntityDescriptor())); assertNotNull(result); - assertTrue(result instanceof OWLClassR); + assertInstanceOf(OWLClassR.class, result); verify(typesMock).getTypes(eq(idResource), eq(Collections.emptySet()), eq(false)); } @@ -626,7 +677,7 @@ void loadReferenceUsesContextWhenGettingDataFromConnection() throws Exception { .thenReturn(new OWLClassA(IDENTIFIER)); when(connectionMock.contains( new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createClassAssertion(false), - new Value<>(NamedResource.create(Vocabulary.c_OwlClassA))), + new Value<>(NamedResource.create(Vocabulary.c_OwlClassA))), Collections.singleton(context))) .thenReturn(true); final OWLClassA result = mapper @@ -644,7 +695,7 @@ void loadReferenceUsesContextWhenGettingTypesFromConnection() throws Exception { final NamedResource idResource = NamedResource.create(IDENTIFIER); final Set> typesAxioms = Collections.singleton( new AxiomImpl<>(idResource, Assertion.createClassAssertion(false), - new Value<>(URI.create(Vocabulary.C_OWLClassR)))); + new Value<>(URI.create(Vocabulary.C_OWLClassR)))); when(typesMock.getTypes(idResource, Collections.singleton(context), false)).thenReturn(typesAxioms); final OWLClassS result = mapper.loadReference(new LoadingParameters<>(OWLClassS.class, IDENTIFIER, descriptor)); assertNotNull(result); @@ -655,6 +706,6 @@ void loadReferenceUsesContextWhenGettingTypesFromConnection() throws Exception { void getEntityFromCacheOrOntologyThrowsEntityExistsWhenObjectIsAlreadyRegisteredUnderDifferentType() { mapper.registerInstance(IDENTIFIER, entityA); assertThrows(OWLEntityExistsException.class, - () -> mapper.getEntityFromCacheOrOntology(OWLClassB.class, IDENTIFIER, aDescriptor)); + () -> mapper.getEntityFromCacheOrOntology(OWLClassB.class, IDENTIFIER, aDescriptor)); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralAnnotationPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralAnnotationPropertyStrategyTest.java index 003cb7014..d45796325 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralAnnotationPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralAnnotationPropertyStrategyTest.java @@ -97,7 +97,7 @@ void addValueFromAxiomAddsStringValueToValues() { final PluralAnnotationPropertyStrategy sut = strategyForN(); final String value = "test"; final Axiom axiom = new AxiomImpl<>(INDIVIDUAL, createAnnotationAssertionForN(), new Value<>(value)); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final OWLClassN instance = new OWLClassN(); sut.buildInstanceFieldValue(instance); @@ -114,7 +114,7 @@ void addValueFromAxiomAddsNamedResourceToValues() { final NamedResource value = NamedResource.create(Generators.createIndividualIdentifier()); final Axiom axiom = new AxiomImpl<>(INDIVIDUAL, createAnnotationAssertionForN(), new Value<>(value)); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final OWLClassN instance = new OWLClassN(); sut.buildInstanceFieldValue(instance); @@ -128,7 +128,7 @@ void addValueFromAxiomAddsStringToUriValues() throws Exception { final NamedResource value = NamedResource.create(Generators.createIndividualIdentifier()); final Axiom axiom = new AxiomImpl<>(INDIVIDUAL, createAnnotationAssertionForN(), new Value<>(value)); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final WithPluralUriAnnotations instance = new WithPluralUriAnnotations(); sut.buildInstanceFieldValue(instance); @@ -218,7 +218,7 @@ void addValueFromAxiomTransformsValueToLexicalForm() throws Exception { final Integer value = 117; final Axiom axiom = new AxiomImpl<>(INDIVIDUAL, Assertion.createAnnotationPropertyAssertion(URI.create( DC.Terms.SOURCE), false), new Value<>(value)); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final WithPluralStringAnnotations instance = new WithPluralStringAnnotations(); sut.buildInstanceFieldValue(instance); assertEquals(Collections.singleton(value.toString()), instance.sources); @@ -241,7 +241,7 @@ void addValueFromAxiomAcceptsIdentifiersForLexicalFormAttribute() throws Excepti final Axiom axiom = new AxiomImpl<>(INDIVIDUAL, Assertion.createAnnotationPropertyAssertion(URI.create( DC.Terms.SOURCE), false), new Value<>(NamedResource.create(value))); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final WithPluralStringAnnotations instance = new WithPluralStringAnnotations(); sut.buildInstanceFieldValue(instance); assertEquals(Collections.singleton(value.toString()), instance.sources); @@ -256,7 +256,7 @@ void addAxiomValueConvertsNamedResourceToUriForAttributeOfTypeObject() throws Ex final URI identifier = Generators.createIndividualIdentifier(); final Axiom axiom = new AxiomImpl<>(NamedResource.create(PK), createAnnotationAssertionForN(), new Value<>(NamedResource.create(identifier))); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final ClassWithObjectAnnotation instance = new ClassWithObjectAnnotation(); sut.buildInstanceFieldValue(instance); assertEquals(Collections.singleton(identifier), instance.pluralAnnotation); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralDataPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralDataPropertyStrategyTest.java index 2b2478473..3751d9d6e 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralDataPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralDataPropertyStrategyTest.java @@ -53,7 +53,6 @@ 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.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; @@ -89,7 +88,7 @@ void setUp() throws Exception { @Test void buildFieldValueCreatesCorrectCollectionTypeForSet() { final PluralDataPropertyStrategy strategy = createStrategyForM(); - strategy.addValueFromAxiom(createMSetAxiom()); + strategy.addAxiomValue(createMSetAxiom()); final OWLClassM m = new OWLClassM(); strategy.buildInstanceFieldValue(m); assertNotNull(m.getIntegerSet()); @@ -123,7 +122,7 @@ private Set addValuesFromAxioms(PluralDataPropertyStrategy s for (int i = 0; i < Generators.randomPositiveInt(10); i++) { final Axiom axiom = createMSetAxiom(); values.add(axiom.getValue().getValue()); - strategy.addValueFromAxiom(axiom); + strategy.addAxiomValue(axiom); } return values; } @@ -133,10 +132,10 @@ void addValueFromAxiomSkipsValuesWithInvalidRange() { final PluralDataPropertyStrategy strategy = createStrategyForM(); final Set values = addValuesFromAxioms(strategy); final Assertion a = assertionForMIntegerSet(); - strategy.addValueFromAxiom(new AxiomImpl<>(INDIVIDUAL, a, new Value<>("Test"))); - strategy.addValueFromAxiom( + strategy.addAxiomValue(new AxiomImpl<>(INDIVIDUAL, a, new Value<>("Test"))); + strategy.addAxiomValue( new AxiomImpl<>(INDIVIDUAL, a, new Value<>(Generators.createIndividualIdentifier()))); - strategy.addValueFromAxiom(new AxiomImpl<>(INDIVIDUAL, a, new Value<>(new Date()))); + strategy.addAxiomValue(new AxiomImpl<>(INDIVIDUAL, a, new Value<>(new Date()))); final OWLClassM m = new OWLClassM(); strategy.buildInstanceFieldValue(m); @@ -144,11 +143,12 @@ void addValueFromAxiomSkipsValuesWithInvalidRange() { } @Test - void buildFieldValueDoesNothingWhenNoValuesWereAdded() { + void buildFieldValueSetsEmptyCollectionWhenNoValuesWereAdded() { final PluralDataPropertyStrategy strategy = createStrategyForM(); final OWLClassM m = new OWLClassM(); strategy.buildInstanceFieldValue(m); - assertNull(m.getIntegerSet()); + assertNotNull(m.getIntegerSet()); + assertTrue(m.getIntegerSet().isEmpty()); } @Test @@ -227,7 +227,7 @@ void buildAxiomsSetsLanguageTagAccordingToPUConfigurationWhenItIsNotSpecifiedInD void addValueFromAxiomUsesConverterToTransformValueToCorrectType() { final PluralDataPropertyStrategy strategy = createStrategyForM(); final Axiom axiom = new AxiomImpl<>(INDIVIDUAL, assertionForMIntegerSet(), new Value<>((short) 117)); - strategy.addValueFromAxiom(axiom); + strategy.addAxiomValue(axiom); final OWLClassM m = new OWLClassM(); strategy.buildInstanceFieldValue(m); assertEquals(Collections.singleton(117), m.getIntegerSet()); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralMultilingualStringFieldStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralMultilingualStringFieldStrategyTest.java index 7af8c06b5..5975e469e 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralMultilingualStringFieldStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/PluralMultilingualStringFieldStrategyTest.java @@ -28,20 +28,37 @@ import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.ontodriver.descriptor.AxiomValueDescriptor; -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.LangString; +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.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import java.net.URI; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) class PluralMultilingualStringFieldStrategyTest { private static final String LANG = "en"; @@ -56,7 +73,6 @@ class PluralMultilingualStringFieldStrategyTest { @BeforeEach void setUp() throws Exception { - MockitoAnnotations.openMocks(this); final Configuration configuration = new Configuration( Collections.singletonMap(JOPAPersistenceProperties.LANG, LANG)); when(mapperMock.getConfiguration()).thenReturn(configuration); @@ -67,8 +83,8 @@ void setUp() throws Exception { private PluralMultilingualStringFieldStrategy createStrategy() { return new PluralMultilingualStringFieldStrategy<>(mocks.forOwlClassU().entityType(), - mocks.forOwlClassU().uPluralStringAtt(), descriptor, - mapperMock); + mocks.forOwlClassU().uPluralStringAtt(), descriptor, + mapperMock); } @Test @@ -141,11 +157,11 @@ void addValueFromAxiomAddsDifferentTranslationsToSameElement() { final Assertion assertion = Assertion .createDataPropertyAssertion(URI.create(Vocabulary.P_U_PLURAL_MULTILINGUAL_ATTRIBUTE), false); final Axiom axOne = new AxiomImpl<>(INDIVIDUAL, assertion, - new Value<>(new LangString("construction", "en"))); + new Value<>(new LangString("construction", "en"))); final Axiom axTwo = new AxiomImpl<>(INDIVIDUAL, assertion, - new Value<>(new LangString("stavba", "cs"))); - sut.addValueFromAxiom(axOne); - sut.addValueFromAxiom(axTwo); + new Value<>(new LangString("stavba", "cs"))); + sut.addAxiomValue(axOne); + sut.addAxiomValue(axTwo); sut.buildInstanceFieldValue(u); assertEquals(1, u.getPluralStringAtt().size()); final MultilingualString msResult = u.getPluralStringAtt().iterator().next(); @@ -160,11 +176,11 @@ void addValueFromAxiomAddsValueInSameLanguageAsNewElementIntoCollection() { final Assertion assertion = Assertion .createDataPropertyAssertion(URI.create(Vocabulary.P_U_PLURAL_MULTILINGUAL_ATTRIBUTE), false); final Axiom axOne = new AxiomImpl<>(INDIVIDUAL, assertion, - new Value<>(new LangString("construction", "en"))); + new Value<>(new LangString("construction", "en"))); final Axiom axTwo = new AxiomImpl<>(INDIVIDUAL, assertion, - new Value<>(new LangString("building", "en"))); - sut.addValueFromAxiom(axOne); - sut.addValueFromAxiom(axTwo); + new Value<>(new LangString("building", "en"))); + sut.addAxiomValue(axOne); + sut.addAxiomValue(axTwo); sut.buildInstanceFieldValue(u); assertEquals(2, u.getPluralStringAtt().size()); assertTrue(u.getPluralStringAtt().stream() @@ -180,14 +196,14 @@ void addValueFromAxiomAddsTranslationsToExistingElements() { final Assertion assertion = Assertion .createDataPropertyAssertion(URI.create(Vocabulary.P_U_PLURAL_MULTILINGUAL_ATTRIBUTE), false); final Axiom axOne = new AxiomImpl<>(INDIVIDUAL, assertion, - new Value<>(new LangString("construction", "en"))); + new Value<>(new LangString("construction", "en"))); final Axiom axTwo = new AxiomImpl<>(INDIVIDUAL, assertion, - new Value<>(new LangString("building", "en"))); + new Value<>(new LangString("building", "en"))); final Axiom axThree = new AxiomImpl<>(INDIVIDUAL, assertion, - new Value<>(new LangString("stavba", "cs"))); - sut.addValueFromAxiom(axOne); - sut.addValueFromAxiom(axTwo); - sut.addValueFromAxiom(axThree); + new Value<>(new LangString("stavba", "cs"))); + sut.addAxiomValue(axOne); + sut.addAxiomValue(axTwo); + sut.addAxiomValue(axThree); sut.buildInstanceFieldValue(u); assertEquals(2, u.getPluralStringAtt().size()); assertTrue( @@ -198,11 +214,12 @@ void addValueFromAxiomAddsTranslationsToExistingElements() { } @Test - void buildInstanceFieldValueLeavesFieldNullWhenNoValuesWereAdded() { + void buildInstanceFieldValueSetsFieldValueToEmptyCollectionWhenNoValuesWereAdded() { final PluralMultilingualStringFieldStrategy sut = createStrategy(); final OWLClassU u = new OWLClassU(ID); sut.buildInstanceFieldValue(u); - assertNull(u.getPluralStringAtt()); + assertNotNull(u.getPluralStringAtt()); + assertTrue(u.getPluralStringAtt().isEmpty()); } @Test @@ -222,14 +239,8 @@ void buildAxiomsFromInstanceReturnsAxiomsCorrespondingToAttributeValue() { final Assertion assertion = Assertion .createDataPropertyAssertion(URI.create(Vocabulary.P_U_PLURAL_MULTILINGUAL_ATTRIBUTE), false); u.getPluralStringAtt().forEach(ms -> - ms.getValue().forEach((lang, val) -> assertThat(result, - hasItem(new AxiomImpl<>( - INDIVIDUAL, - assertion, - new Value<>( - new LangString( - val, - lang))))))); + ms.getValue().forEach((lang, val) -> assertThat(result, + hasItem(new AxiomImpl<>(INDIVIDUAL, assertion, new Value<>(new LangString(val, lang))))))); } @Test 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 b3bd679be..3845f1f3a 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 @@ -17,7 +17,12 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.environment.*; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassC; +import cz.cvut.kbss.jopa.environment.OWLClassJ; +import cz.cvut.kbss.jopa.environment.OWLClassP; +import cz.cvut.kbss.jopa.environment.OneOfEnum; +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.IRI; @@ -30,8 +35,14 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.Identifier; import cz.cvut.kbss.jopa.oom.converter.ObjectOneOfEnumConverter; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingSetProxy; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.jopa.vocabulary.OWL; -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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -52,8 +63,15 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItems; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +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) @MockitoSettings(strictness = Strictness.LENIENT) @@ -86,7 +104,7 @@ void addValueFromAxiomLoadsInstanceAndAddsItIntoAttributeCollection() { URI.create(Vocabulary.P_HAS_A), false), new Value<>( NamedResource.create(aReference))); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); sut.buildInstanceFieldValue(instance); assertNotNull(instance.getOwlClassA()); assertEquals(1, instance.getOwlClassA().size()); @@ -111,9 +129,10 @@ void addValueFromAxiomDoesNothingWhenInstanceCannotBeLoaded() { URI.create(Vocabulary.P_HAS_A), false), new Value<>( NamedResource.create(aReference))); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); sut.buildInstanceFieldValue(instance); - assertNull(instance.getOwlClassA()); + assertNotNull(instance.getOwlClassA()); + assertTrue(instance.getOwlClassA().isEmpty()); verify(mapperMock).getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any(Descriptor.class)); } @@ -130,7 +149,7 @@ void addValueFromAxiomGetsAttributeDescriptorFromEntityDescriptorForLoading() { URI.create(Vocabulary.P_HAS_A), false), new Value<>( NamedResource.create(aReference))); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, aReference, aDescriptor); } @@ -228,7 +247,7 @@ void addValueFromAxiomConvertsNamedResourceToEnumConstantForEnumValuedObjectProp final SimpleSetPropertyStrategy sut = initEnumAttributeStrategy(); final NamedResource value = NamedResource.create(OWL.ANNOTATION_PROPERTY); - sut.addValueFromAxiom(new AxiomImpl<>(NamedResource.create(ID), Assertion.createObjectPropertyAssertion( + sut.addAxiomValue(new AxiomImpl<>(NamedResource.create(ID), Assertion.createObjectPropertyAssertion( URI.create(Vocabulary.p_m_objectOneOfEnumAttribute), false), new Value<>(value))); final EntityWithPluralObjectPropertyEnum instance = new EntityWithPluralObjectPropertyEnum(); sut.buildInstanceFieldValue(instance); @@ -276,4 +295,31 @@ void buildAxiomsFromInstanceConvertsEnumConstantsToNamedResourcesForEnumValuedOb 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 OWLClassJ instance = new OWLClassJ(ID); + instance.setOwlClassA(new LazyLoadingSetProxy<>(instance, mocks.forOwlClassJ().setAttribute(), mock(UnitOfWork.class))); + sut.buildInstanceFieldValue(instance); + assertNotNull(instance.getOwlClassA()); + assertTrue(instance.getOwlClassA().isEmpty()); + } + + @Test + void lazilyAddAxiomValueSetsHasValueButDoesNotLoadReference() { + final SimpleSetPropertyStrategy sut = new SimpleSetPropertyStrategy<>(mocks.forOwlClassJ().entityType(), mocks.forOwlClassJ() + .setAttribute(), descriptor, mapperMock); + final URI aReference = Generators.createIndividualIdentifier(); + final Axiom axiom = new AxiomImpl<>(NamedResource.create(ID), + Assertion.createObjectPropertyAssertion( + URI.create(Vocabulary.P_HAS_A), false), new Value<>( + NamedResource.create(aReference))); + + sut.lazilyAddAxiomValue(axiom); + verify(mapperMock, never()).getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(aReference), any()); + assertTrue(sut.hasValue()); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ReferencedListDataPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ReferencedListDataPropertyStrategyTest.java index 54b63cdc4..6c38552e6 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ReferencedListDataPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/ReferencedListDataPropertyStrategyTest.java @@ -14,6 +14,8 @@ 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.proxy.lazy.LazyLoadingListProxy; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.ontodriver.Connection; import cz.cvut.kbss.ontodriver.Lists; import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor; @@ -84,8 +86,8 @@ static ListAttributeImpl initDataListAttrib when(att.getIRI()).thenReturn(IRI.create(Vocabulary.ATTRIBUTE_BASE + "hasDataList")); when(att.getCollectionType()).thenReturn(CollectionType.LIST); when(att.getJavaField()).thenReturn(DataPropertyReferencedList.class.getDeclaredField("list")); - when(att.getOWLObjectPropertyHasNextIRI()).thenReturn(IRI.create(SequencesVocabulary.s_p_hasNext)); - when(att.getOWLPropertyHasContentsIRI()).thenReturn(IRI.create(SequencesVocabulary.s_p_hasContents)); + when(att.getHasNextPropertyIRI()).thenReturn(IRI.create(SequencesVocabulary.s_p_hasNext)); + when(att.getHasContentsPropertyIRI()).thenReturn(IRI.create(SequencesVocabulary.s_p_hasContents)); when(att.getConverter()).thenReturn(Converters.getDefaultConverter(Integer.class).get()); return att; } @@ -116,4 +118,16 @@ void buildAxiomValuesFromInstanceHandlesDataPropertyValues() throws Exception { assertEquals(Assertion.AssertionType.DATA_PROPERTY, nodeContentAssertion.getType()); } + @Test + void buildInstanceFieldValueSetsInstanceFieldValueToEmptyListWhenNoValuesWereAdded() throws Exception { + final EntityType et = mock(EntityType.class); + final ListAttributeImpl att = initDataListAttribute(); + final ReferencedListDataPropertyStrategy sut = new ReferencedListDataPropertyStrategy<>(et, att, descriptor, mapperMock); + final DataPropertyReferencedList instance = new DataPropertyReferencedList(); + instance.uri = Generators.createIndividualIdentifier(); + instance.list = new LazyLoadingListProxy<>(instance, att, mock(UnitOfWork.class)); + sut.buildInstanceFieldValue(instance); + assertNotNull(instance.list); + assertTrue(instance.list.isEmpty()); + } } 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 3347ec825..867cc6d66 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 @@ -90,7 +90,7 @@ void buildsInstanceFieldFromAxiomsIncludingNodes() throws Exception { final OWLClassC c = new OWLClassC(IDENTIFIER); final List> axioms = initRefListAxioms(true); when(mapperMock.loadReferencedList(any(ReferencedListDescriptor.class))).thenReturn(axioms); - strategy.addValueFromAxiom(axioms.iterator().next()); + strategy.addAxiomValue(axioms.iterator().next()); assertNull(c.getReferencedList()); strategy.buildInstanceFieldValue(c); @@ -101,9 +101,9 @@ void buildsInstanceFieldFromAxiomsIncludingNodes() throws Exception { final ReferencedListDescriptor listDescriptor = captor.getValue(); assertEquals(IDENTIFIER, listDescriptor.getListOwner().getIdentifier()); assertEquals(refListMock.getIRI().toURI(), listDescriptor.getListProperty().getIdentifier()); - assertEquals(refListMock.getOWLObjectPropertyHasNextIRI().toURI(), listDescriptor.getNextNode() - .getIdentifier()); - assertEquals(refListMock.getOWLPropertyHasContentsIRI().toURI(), listDescriptor + assertEquals(refListMock.getHasNextPropertyIRI().toURI(), listDescriptor.getNextNode() + .getIdentifier()); + assertEquals(refListMock.getHasContentsPropertyIRI().toURI(), listDescriptor .getNodeContent().getIdentifier()); } @@ -124,7 +124,7 @@ private List> initRefListAxioms(boolean includeNodes) throws Exception node = new AxiomImpl<>( previous, Assertion.createObjectPropertyAssertion(refListMock - .getOWLObjectPropertyHasNextIRI().toURI(), + .getHasNextPropertyIRI().toURI(), refListMock.isInferred()), new Value<>(nodeUri)); } @@ -132,7 +132,7 @@ private List> initRefListAxioms(boolean includeNodes) throws Exception } final Axiom content = new AxiomImpl<>(nodeUri, Assertion.createObjectPropertyAssertion( - refListMock.getOWLPropertyHasContentsIRI() + refListMock.getHasContentsPropertyIRI() .toURI(), refListMock.isInferred()), new Value<>(NamedResource.create(a.getUri()))); when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, a.getUri(), @@ -152,7 +152,7 @@ void buildsInstanceFieldFromAxiomsWithoutNodes() throws Exception { final OWLClassC c = new OWLClassC(IDENTIFIER); final List> axioms = initRefListAxioms(false); when(mapperMock.loadReferencedList(any(ReferencedListDescriptor.class))).thenReturn(axioms); - strategy.addValueFromAxiom(axioms.iterator().next()); + strategy.addAxiomValue(axioms.iterator().next()); assertNull(c.getReferencedList()); strategy.buildInstanceFieldValue(c); @@ -169,7 +169,7 @@ void buildsInstanceFieldWithPlainIdentifiers() throws Exception { final List> axioms = initRefListAxioms(true); when(mapperMock.loadReferencedList(any(ReferencedListDescriptor.class))).thenReturn(axioms); - strategy.addValueFromAxiom(axioms.iterator().next()); + strategy.addAxiomValue(axioms.iterator().next()); final OWLClassP p = new OWLClassP(); p.setUri(IDENTIFIER); strategy.buildInstanceFieldValue(p); @@ -195,11 +195,11 @@ void extractsValuesIntoAxiomsForSave() throws Exception { URI.create(OWLClassC.getRefListField().getAnnotation(OWLObjectProperty.class).iri()), refListMock.isInferred())); assertEquals(res.getNextNode(), Assertion.createObjectPropertyAssertion(refListMock - .getOWLObjectPropertyHasNextIRI() + .getHasNextPropertyIRI() .toURI(), refListMock.isInferred())); assertEquals(res.getNodeContent(), Assertion.createObjectPropertyAssertion(refListMock - .getOWLPropertyHasContentsIRI() + .getHasContentsPropertyIRI() .toURI(), refListMock.isInferred())); final List expected = list.stream().map(OWLClassA::getUri).collect(Collectors.toList()); 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 23ebef694..ab08e0356 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 @@ -92,7 +92,7 @@ void buildsInstanceFieldFromAxioms() { when(mapperMock.loadSimpleList(any(SimpleListDescriptor.class))).thenReturn(axioms); - strategy.addValueFromAxiom(ax); + strategy.addAxiomValue(ax); final OWLClassC instance = new OWLClassC(); instance.setUri(IDENTIFIER); strategy.buildInstanceFieldValue(instance); @@ -108,7 +108,7 @@ private Collection> buildAxiomsForList(ListAttribute for (OWLClassA item : lst) { final Axiom a = new AxiomImpl<>( NamedResource.create(previous), - Assertion.createObjectPropertyAssertion(la.getOWLObjectPropertyHasNextIRI().toURI(), false), + Assertion.createObjectPropertyAssertion(la.getHasNextPropertyIRI().toURI(), false), new Value<>(NamedResource.create(item.getUri()))); axioms.add(a); when(mapperMock.getEntityFromCacheOrOntology(OWLClassA.class, item.getUri(), @@ -132,7 +132,7 @@ void buildsInstanceFieldOfPlainIdentifiersFromAxioms() { final Collection> axioms = buildAxiomsForList(simpleList, as); when(mapperMock.loadSimpleList(any(SimpleListDescriptor.class))).thenReturn(axioms); - strategy.addValueFromAxiom(ax); + strategy.addAxiomValue(ax); final OWLClassP instance = new OWLClassP(); instance.setUri(IDENTIFIER); strategy.buildInstanceFieldValue(instance); @@ -153,14 +153,14 @@ void addsValueFromAxiomAndVerifiesCorrectDescriptorWasCreated() { when(mapperMock.loadSimpleList(any(SimpleListDescriptor.class))) .thenReturn(axioms); - strategy.addValueFromAxiom(ax); + strategy.addAxiomValue(ax); final ArgumentCaptor captor = ArgumentCaptor .forClass(SimpleListDescriptor.class); verify(mapperMock).loadSimpleList(captor.capture()); final SimpleListDescriptor res = captor.getValue(); assertEquals(IDENTIFIER, res.getListOwner().getIdentifier()); assertEquals(simpleList.getIRI().toURI(), res.getListProperty().getIdentifier()); - assertEquals(simpleList.getOWLObjectPropertyHasNextIRI().toURI(), res + assertEquals(simpleList.getHasNextPropertyIRI().toURI(), res .getNextNode().getIdentifier()); assertNull(res.getContext()); } @@ -176,8 +176,8 @@ void extractsListValuesForSave() throws Exception { assertEquals(simpleListField.getAnnotation(OWLObjectProperty.class) .iri(), res.getListProperty().getIdentifier().toString()); assertEquals(simpleListField.getAnnotation(Sequence.class) - .ObjectPropertyHasNextIRI(), res.getNextNode().getIdentifier() - .toString()); + .hasNextPropertyIRI(), res.getNextNode().getIdentifier() + .toString()); assertEquals(c.getSimpleList().size(), res.getValues().size()); for (int i = 0; i < c.getSimpleList().size(); i++) { assertEquals(c.getSimpleList().get(i).getUri(), res.getValues() @@ -202,7 +202,7 @@ void extractsListValuesForSaveListIsEmpty() throws Exception { final Field simpleListField = OWLClassC.getSimpleListField(); assertEquals(simpleListField.getAnnotation(OWLObjectProperty.class).iri(), res.getListProperty().getIdentifier().toString()); - assertEquals(simpleListField.getAnnotation(Sequence.class).ObjectPropertyHasNextIRI(), + assertEquals(simpleListField.getAnnotation(Sequence.class).hasNextPropertyIRI(), res.getNextNode().getIdentifier().toString()); assertTrue(res.getValues().isEmpty()); } @@ -217,7 +217,7 @@ void extractsListValuesForSaveListIsNull() throws Exception { final Field simpleListField = OWLClassC.getSimpleListField(); assertEquals(simpleListField.getAnnotation(OWLObjectProperty.class).iri(), res.getListProperty().getIdentifier().toString()); - assertEquals(simpleListField.getAnnotation(Sequence.class).ObjectPropertyHasNextIRI(), + assertEquals(simpleListField.getAnnotation(Sequence.class).hasNextPropertyIRI(), res.getNextNode().getIdentifier().toString()); assertTrue(res.getValues().isEmpty()); } 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 c812faada..596d7970d 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 @@ -189,7 +189,7 @@ void buildsInstanceFieldAsSetOfUrls() throws Exception { URI.create(OWLClassP.getIndividualUrlsField().getAnnotation(OWLObjectProperty.class).iri()); final Set values = generateSet(true); final Collection> axioms = buildAxiomsForSet(property, values); - axioms.forEach(strategy::addValueFromAxiom); + axioms.forEach(strategy::addAxiomValue); final OWLClassP p = new OWLClassP(); strategy.buildInstanceFieldValue(p); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularAnnotationPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularAnnotationPropertyStrategyTest.java index 96f45895e..e23f1a37b 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularAnnotationPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularAnnotationPropertyStrategyTest.java @@ -95,7 +95,7 @@ void addAxiomValueAddsStringAnnotationValue() { final SingularAnnotationPropertyStrategy strategy = forN(mocks.forOwlClassN().annotationAttribute()); final Axiom ax = new AxiomImpl<>(NamedResource.create(ID), annotationForN(), new Value<>(str)); - strategy.addValueFromAxiom(ax); + strategy.addAxiomValue(ax); final OWLClassN n = new OWLClassN(); strategy.buildInstanceFieldValue(n); assertEquals(str, n.getAnnotationProperty()); @@ -116,7 +116,7 @@ void addAxiomValueSkipsAxiomWhoseValueDoesNotMatchTargetFieldType() { final SingularAnnotationPropertyStrategy strategy = forN(mocks.forOwlClassN().annotationAttribute()); final Axiom ax = new AxiomImpl<>(NamedResource.create(ID), annotationForN(), new Value<>(117)); - strategy.addValueFromAxiom(ax); + strategy.addAxiomValue(ax); final OWLClassN n = new OWLClassN(); strategy.buildInstanceFieldValue(n); assertNull(n.getAnnotationProperty()); @@ -130,7 +130,7 @@ void addAxiomValueAddsPlainIdentifierValueOfAnnotationProperty() { final Axiom ax = new AxiomImpl<>(NamedResource.create(ID), annotationWithUriForN(), new Value<>(NamedResource.create(value))); - strategy.addValueFromAxiom(ax); + strategy.addAxiomValue(ax); final OWLClassN n = new OWLClassN(); strategy.buildInstanceFieldValue(n); assertEquals(value, n.getAnnotationUri()); @@ -149,7 +149,7 @@ void addAxiomValueThrowsIntegrityConstraintsViolationWhenAnotherValueIsAlreadySe axioms.add(new AxiomImpl<>(NamedResource.create(ID), annotationForN(), new Value<>("String" + i))); } - assertThrows(IntegrityConstraintViolatedException.class, () -> axioms.forEach(strategy::addValueFromAxiom)); + assertThrows(IntegrityConstraintViolatedException.class, () -> axioms.forEach(strategy::addAxiomValue)); } @Test @@ -236,7 +236,7 @@ void addAxiomValueTransformsValueToLexicalForm() { final Integer value = 117; final Axiom axiom = new AxiomImpl<>(NamedResource.create(ID), lexicalFormAssertion(), new Value<>(value)); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final OWLClassM result = new OWLClassM(); result.setKey(ID.toString()); sut.buildInstanceFieldValue(result); @@ -257,7 +257,7 @@ void addAxiomValueAcceptsNamedResourceForLexicalFormAttribute() { final URI identifier = Generators.createIndividualIdentifier(); final Axiom axiom = new AxiomImpl<>(NamedResource.create(ID), lexicalFormAssertion(), new Value<>(NamedResource.create(identifier))); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final OWLClassM result = new OWLClassM(); result.setKey(ID.toString()); sut.buildInstanceFieldValue(result); @@ -273,7 +273,7 @@ void addAxiomValueConvertsNamedResourceToUriForAttributeOfTypeObject() throws Ex final URI identifier = Generators.createIndividualIdentifier(); final Axiom axiom = new AxiomImpl<>(NamedResource.create(ID), annotationWithUriForN(), new Value<>(NamedResource.create(identifier))); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final ClassWithObjectAnnotation instance = new ClassWithObjectAnnotation(); sut.buildInstanceFieldValue(instance); assertEquals(identifier, instance.singularAnnotation); @@ -348,7 +348,7 @@ void addAxiomValueConvertsLangStringToMultilingualStringWhenTargetIsObjectAndMul final LangString langString = new LangString("test", "en"); final Axiom axiom = new AxiomImpl<>(NamedResource.create(ID), annotationWithUriForN(), new Value<>(langString)); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final ClassWithObjectAnnotation instance = new ClassWithObjectAnnotation(); sut.buildInstanceFieldValue(instance); assertEquals(MultilingualString.create(langString.getValue(), langString.getLanguage().get()), diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularDataPropertyStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularDataPropertyStrategyTest.java index aa06a846b..3553fad04 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularDataPropertyStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularDataPropertyStrategyTest.java @@ -101,7 +101,7 @@ void buildInstanceFieldValueTransformsOffsetDateTimeToLocalDateTime() { final OffsetDateTime value = OffsetDateTime.now(); final Axiom axiom = new AxiomImpl<>(NamedResource.create(PK), strategy.createAssertion(), new Value<>(value)); - strategy.addValueFromAxiom(axiom); + strategy.addAxiomValue(axiom); strategy.buildInstanceFieldValue(t); assertNotNull(t.getLocalDateTime()); assertEquals(value.toLocalDateTime(), t.getLocalDateTime()); @@ -117,7 +117,7 @@ void buildInstanceFieldConvertsRepositoryValueToEnum() { final Axiom axiom = new AxiomImpl<>(NamedResource.create(PK), sut.createAssertion(), new Value<>(OWLClassM.Severity.MEDIUM.toString())); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); sut.buildInstanceFieldValue(m); assertEquals(OWLClassM.Severity.MEDIUM, m.getEnumAttribute()); } @@ -133,7 +133,7 @@ void buildInstanceFieldTransformsValueToLexicalForm() { final Integer value = 117; final Axiom axiom = new AxiomImpl<>(NamedResource.create(PK), sut.createAssertion(), new Value<>(value)); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); sut.buildInstanceFieldValue(m); assertEquals(value.toString(), m.getLexicalForm()); } @@ -165,7 +165,7 @@ void buildInstanceFieldTransformsUsingCustomConverter() { final String value = "-01:00"; final Axiom axiom = new AxiomImpl<>(NamedResource.create(PK), sut.createAssertion(), new Value<>(value)); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); sut.buildInstanceFieldValue(m); assertEquals(ZoneOffset.of(value), m.getWithConverter()); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularMultilingualStringFieldStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularMultilingualStringFieldStrategyTest.java index 6871b45dd..477d0dda1 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularMultilingualStringFieldStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/SingularMultilingualStringFieldStrategyTest.java @@ -68,7 +68,7 @@ void addValueFromAxiomCreatesMultilingualStringInstanceForLangStringValue() { Vocabulary.P_U_SINGULAR_MULTILINGUAL_ATTRIBUTE), false), new Value<>(new LangString(value, LANG))); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final OWLClassU instance = new OWLClassU(); sut.buildInstanceFieldValue(instance); assertNotNull(instance.getSingularStringAtt()); @@ -94,8 +94,8 @@ void addValueFromAxiomSupportsAddingMultipleValuesWithDifferentLanguageTags() { final Axiom axiomCs = new AxiomImpl<>(INDIVIDUAL, assertion, new Value<>(new LangString(valueCs, "cs"))); - sut.addValueFromAxiom(axiomEn); - sut.addValueFromAxiom(axiomCs); + sut.addAxiomValue(axiomEn); + sut.addAxiomValue(axiomCs); final OWLClassU instance = new OWLClassU(); sut.buildInstanceFieldValue(instance); assertNotNull(instance.getSingularStringAtt()); @@ -113,7 +113,7 @@ void addValueFromAxiomSupportsAddingStringValueWithoutLanguageTag() { Vocabulary.P_U_SINGULAR_MULTILINGUAL_ATTRIBUTE), false), new Value<>(value)); - sut.addValueFromAxiom(axiom); + sut.addAxiomValue(axiom); final OWLClassU instance = new OWLClassU(); sut.buildInstanceFieldValue(instance); assertNotNull(instance.getSingularStringAtt()); @@ -132,8 +132,8 @@ void addValueFromAxiomThrowsCardinalityConstraintViolatedExceptionWhenMultipleVa Vocabulary.P_U_SINGULAR_MULTILINGUAL_ATTRIBUTE), false), new Value<>(new LangString("test two", LANG))); - sut.addValueFromAxiom(axiomOne); - assertThrows(CardinalityConstraintViolatedException.class, () -> sut.addValueFromAxiom(axiomTwo)); + sut.addAxiomValue(axiomOne); + assertThrows(CardinalityConstraintViolatedException.class, () -> sut.addAxiomValue(axiomTwo)); } @Test @@ -148,8 +148,8 @@ void addValueFromAxiomSupportsAddingLanguageTaggedAndLanguageLessValues() { final Axiom axiomCs = new AxiomImpl<>(INDIVIDUAL, assertion, new Value<>(new LangString(value))); - sut.addValueFromAxiom(axiomEn); - sut.addValueFromAxiom(axiomCs); + sut.addAxiomValue(axiomEn); + sut.addAxiomValue(axiomCs); final OWLClassU instance = new OWLClassU(); sut.buildInstanceFieldValue(instance); assertNotNull(instance.getSingularStringAtt()); @@ -169,8 +169,8 @@ void addValueFromAxiomThrowsCardinalityConstraintViolatedExceptionWhenMultipleSi Vocabulary.P_U_SINGULAR_MULTILINGUAL_ATTRIBUTE), false), new Value<>(new LangString("test two"))); - sut.addValueFromAxiom(axiomOne); - assertThrows(CardinalityConstraintViolatedException.class, () -> sut.addValueFromAxiom(axiomTwo)); + sut.addAxiomValue(axiomOne); + assertThrows(CardinalityConstraintViolatedException.class, () -> sut.addAxiomValue(axiomTwo)); } @Test 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 441718f37..06fd7f5de 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 @@ -78,9 +78,9 @@ void buildInstanceFieldSupportsPlainIdentifierValues() throws Exception { final FieldStrategy, OWLClassP> strategy = strategy(metamodelMocks.forOwlClassP().entityType(), metamodelMocks.forOwlClassP().pUriAttribute()); strategy.setReferenceSavingResolver(referenceResolverMock); - strategy.addValueFromAxiom( + strategy.addAxiomValue( new AxiomImpl<>(NamedResource.create(IDENTIFIER), propertyP(), - new Value<>(NamedResource.create(VALUE)))); + new Value<>(NamedResource.create(VALUE)))); final OWLClassP p = new OWLClassP(); strategy.buildInstanceFieldValue(p); assertEquals(VALUE, p.getIndividualUri()); @@ -143,7 +143,7 @@ void buildAxiomValuesRegistersPendingChangeWhenReferenceCannotBeSavedDirectly() verify(referenceResolverMock) .registerPendingReference(NamedResource.create(IDENTIFIER), strategy.createAssertion(), - d.getOwlClassA(), null); + d.getOwlClassA(), null); } @Test @@ -205,15 +205,15 @@ void addValueThrowsCardinalityViolationWhenValueIsAlreadyPresent() { final OWLClassA existing = Generators.generateOwlClassAInstance(); when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(VALUE), any())).thenReturn(existing); final Assertion assertion = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false); - sut.addValueFromAxiom( + sut.addAxiomValue( new AxiomImpl<>(NamedResource.create(IDENTIFIER), assertion, new Value<>(NamedResource.create(VALUE)))); final OWLClassA another = Generators.generateOwlClassAInstance(); when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(another.getUri()), any())) .thenReturn(another); final Axiom violationAxiom = new AxiomImpl<>(NamedResource.create(IDENTIFIER), assertion, - new Value<>( - NamedResource.create(another.getUri()))); - assertThrows(CardinalityConstraintViolatedException.class, () -> sut.addValueFromAxiom(violationAxiom)); + new Value<>( + NamedResource.create(another.getUri()))); + assertThrows(CardinalityConstraintViolatedException.class, () -> sut.addAxiomValue(violationAxiom)); } @Test @@ -223,12 +223,12 @@ void addValueDoesNothingWhenReferenceInstanceCannotBeLoaded() { final OWLClassA existing = Generators.generateOwlClassAInstance(); when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(VALUE), any())).thenReturn(existing); final Assertion assertion = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false); - sut.addValueFromAxiom( + sut.addAxiomValue( new AxiomImpl<>(NamedResource.create(IDENTIFIER), assertion, new Value<>(NamedResource.create(VALUE)))); final URI another = Generators.createIndividualIdentifier(); when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(another), any())).thenReturn(null); - sut.addValueFromAxiom(new AxiomImpl<>(NamedResource.create(IDENTIFIER), assertion, - new Value<>(NamedResource.create(another)))); + sut.addAxiomValue(new AxiomImpl<>(NamedResource.create(IDENTIFIER), assertion, + new Value<>(NamedResource.create(another)))); final OWLClassD instance = new OWLClassD(); sut.buildInstanceFieldValue(instance); assertSame(existing, instance.getOwlClassA()); @@ -244,7 +244,7 @@ void addValueGetsAttributeDescriptorFromEntityDescriptorForLoading() { final OWLClassA existing = Generators.generateOwlClassAInstance(); when(mapperMock.getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(VALUE), any())).thenReturn(existing); final Assertion assertion = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false); - sut.addValueFromAxiom( + sut.addAxiomValue( new AxiomImpl<>(NamedResource.create(IDENTIFIER), assertion, new Value<>(NamedResource.create(VALUE)))); verify(mapperMock).getEntityFromCacheOrOntology(OWLClassA.class, VALUE, aDescriptor); } @@ -302,8 +302,8 @@ void addValueFromAxiomConvertsNamedResourceToEnumConstantForEnumValuedObjectProp final NamedResource value = NamedResource.create(OWL.ANNOTATION_PROPERTY); final FieldStrategy, OWLClassM> sut = strategy(metamodelMocks.forOwlClassM().entityType(), - metamodelMocks.forOwlClassM().objectOneOfEnumAttribute()); - sut.addValueFromAxiom(new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createObjectPropertyAssertion( + metamodelMocks.forOwlClassM().objectOneOfEnumAttribute()); + sut.addAxiomValue(new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createObjectPropertyAssertion( URI.create(Vocabulary.p_m_objectOneOfEnumAttribute), false), new Value<>(value))); sut.buildInstanceFieldValue(m); assertEquals(OneOfEnum.ANNOTATION_PROPERTY, m.getObjectOneOfEnumAttribute()); @@ -316,12 +316,37 @@ void buildAxiomValuesFromInstanceConvertsEnumValueToNamedResourceForEnumValuedOb m.setObjectOneOfEnumAttribute(OneOfEnum.DATATYPE_PROPERTY); final FieldStrategy, OWLClassM> sut = strategy(metamodelMocks.forOwlClassM().entityType(), - metamodelMocks.forOwlClassM().objectOneOfEnumAttribute()); + metamodelMocks.forOwlClassM().objectOneOfEnumAttribute()); sut.setReferenceSavingResolver(referenceResolverMock); when(referenceResolverMock.shouldSaveReference(m.getObjectOneOfEnumAttribute(), Collections.emptySet())).thenReturn(true); sut.buildAxiomValuesFromInstance(m, gatherer); verify(gatherer).addValue(Assertion.createObjectPropertyAssertion( - URI.create(Vocabulary.p_m_objectOneOfEnumAttribute), false), - new Value<>(NamedResource.create(OWL.DATATYPE_PROPERTY)), null); + URI.create(Vocabulary.p_m_objectOneOfEnumAttribute), false), + new Value<>(NamedResource.create(OWL.DATATYPE_PROPERTY)), null); + } + + @Test + void lazilyAddAxiomValueSetsLazyLoadingPlaceholderAndDoesNotLoadReference() { + final FieldStrategy, OWLClassD> sut = + strategy(metamodelMocks.forOwlClassD().entityType(), metamodelMocks.forOwlClassD().owlClassAAtt()); + sut.setReferenceSavingResolver(referenceResolverMock); + sut.lazilyAddAxiomValue( + new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false), + new Value<>(NamedResource.create(VALUE)))); + verify(mapperMock, never()).getEntityFromCacheOrOntology(eq(OWLClassA.class), eq(VALUE), any()); + assertTrue(sut.hasValue()); + } + + @Test + void buildInstanceFieldAfterLazilyAddingReferenceDoesNotSetFieldValue() { + final FieldStrategy, OWLClassD> sut = + strategy(metamodelMocks.forOwlClassD().entityType(), metamodelMocks.forOwlClassD().owlClassAAtt()); + sut.setReferenceSavingResolver(referenceResolverMock); + sut.lazilyAddAxiomValue( + new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_A), false), + new Value<>(NamedResource.create(VALUE)))); + final OWLClassD target = new OWLClassD(IDENTIFIER); + sut.buildInstanceFieldValue(target); + assertNull(target.getOwlClassA()); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/StringPropertiesFieldStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/StringPropertiesFieldStrategyTest.java index cc11a37f1..01c3a1306 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/StringPropertiesFieldStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/StringPropertiesFieldStrategyTest.java @@ -24,7 +24,11 @@ import cz.cvut.kbss.jopa.exceptions.InvalidAssertionIdentifierException; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -34,10 +38,21 @@ import org.mockito.quality.Strictness; import java.net.URI; -import java.util.*; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.*; +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.Iterator; +import java.util.Map; +import java.util.Set; + +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.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -341,7 +356,7 @@ private Map> prepareForRemove() { public void buildsInstanceFieldFroAxiomValues() { final Map> properties = Generators.generateStringProperties(); final Collection> axioms = createAxiomsForProperties(properties); - axioms.forEach(ax -> strategy.addValueFromAxiom(ax)); + axioms.forEach(ax -> strategy.addAxiomValue(ax)); strategy.buildInstanceFieldValue(entity); assertEquals(properties, entity.getProperties()); } @@ -353,7 +368,7 @@ private Collection> createAxiomsForProperties(Map> axioms.addAll(e.getValue().stream() .map(val -> new AxiomImpl<>(subject, Assertion.createPropertyAssertion(URI.create(e.getKey()), false), new Value<>(val))) - .collect(Collectors.toList())); + .toList()); } return axioms; } @@ -372,4 +387,11 @@ void buildAxiomsFromInstanceReturnsEmptySetWhenAttributeValueIsNull() { assertNotNull(result); assertTrue(result.isEmpty()); } + + @Test + void buildInstanceFieldValueSetsInstanceFieldValueToEmptyMapWhenNoValuesWereAdded() { + strategy.buildInstanceFieldValue(entity); + assertNotNull(entity.getProperties()); + assertTrue(entity.getProperties().isEmpty()); + } } 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 3e22799e3..9ab7cbcb2 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 @@ -22,7 +22,7 @@ import cz.cvut.kbss.jopa.environment.OWLClassS; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; -import cz.cvut.kbss.jopa.sessions.LoadingParameters; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.ontodriver.Types; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.model.Assertion; @@ -72,8 +72,8 @@ void setUp() throws Exception { when(descriptorFactoryMock.createForEntityLoading(loadingParameters, mocks.forOwlClassR().entityType())) .thenReturn(axiomDescriptor); this.instanceLoader = TwoStepInstanceLoader.builder().connection(connectionMock).metamodel(metamodelMock) - .cache(cacheMock).descriptorFactory(descriptorFactoryMock) - .entityBuilder(entityConstructorMock).build(); + .cache(cacheMock).descriptorFactory(descriptorFactoryMock) + .entityBuilder(entityConstructorMock).build(); } @Test @@ -98,7 +98,8 @@ void loadEntityLoadsEntityFromStorageWhenEntityTypeIsDetermined() throws Excepti final Collection> axioms = new HashSet<>(types); when(connectionMock.find(axiomDescriptor)).thenReturn(axioms); when(entityConstructorMock - .reconstructEntity(IDENTIFIER, metamodelMock.entity(OWLClassR.class), descriptor, axioms)) + .reconstructEntity(new EntityConstructor.EntityConstructionParameters<>(IDENTIFIER, metamodelMock.entity(OWLClassR.class), descriptor, false), + axioms)) .thenReturn(entityR); final OWLClassS result = instanceLoader.loadEntity(loadingParameters); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TypedPropertiesFieldStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TypedPropertiesFieldStrategyTest.java index f07fc3626..91f4a7c8a 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TypedPropertiesFieldStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TypedPropertiesFieldStrategyTest.java @@ -24,20 +24,41 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.PropertiesSpecification; -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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import java.net.URI; -import java.util.*; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +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.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) public class TypedPropertiesFieldStrategyTest { private static final URI PK = Generators.createIndividualIdentifier(); @@ -52,7 +73,6 @@ public class TypedPropertiesFieldStrategyTest { @BeforeEach public void setUp() throws Exception { - MockitoAnnotations.openMocks(this); MetamodelMocks mocks = new MetamodelMocks(); this.entity = new OWLClassP(); @@ -138,9 +158,8 @@ public void removesValuesOfSomeProperties() throws Exception { public void addsPropertyWithValues() throws Exception { entity.setProperties(Generators.generateTypedProperties(3, 3)); when(mapperMock.getOriginalInstance(entity)).thenReturn(createOriginal()); - final Map> added = new HashMap<>(); - added.put(URI.create("http://krizik.felk.cvut.cz/ontologies/jopa#added"), - new HashSet<>(Arrays.asList(true, "two", 3))); + final Map> added = Map.of(URI.create("http://krizik.felk.cvut.cz/ontologies/jopa#added"), + Set.of(true, "two", 3)); entity.getProperties().putAll(added); strategy.buildAxiomValuesFromInstance(entity, gatherer); @@ -196,14 +215,12 @@ public void extractsAllPropertiesForAddWhenThereWereNoneInOriginal() throws Exce private Map> prepareForAdd() { final Map> added = new HashMap<>(); final URI property = entity.getProperties().keySet().iterator().next(); - final Set newValues = new HashSet<>( - Arrays.asList(URI.create("http://krizik.felk.cvut.cz/ontologies/test#A"), - URI.create("http://krizik.felk.cvut.cz/ontologies/test#B"))); + final Set newValues = Set.of(URI.create("http://krizik.felk.cvut.cz/ontologies/test#A"), + URI.create("http://krizik.felk.cvut.cz/ontologies/test#B")); added.put(property, newValues); entity.getProperties().get(property).addAll(newValues); - final Map> newProperty = Collections - .singletonMap(URI.create("http://krizik.felk.cvut.cz/ontologies/jopa#newProperty"), - new HashSet<>(Arrays.asList("blablaOne", 2, URI.create("http://blabla.org")))); + final Map> newProperty = Map.of(URI.create("http://krizik.felk.cvut.cz/ontologies/jopa#newProperty"), + Set.of("blablaOne", 2, URI.create("http://blabla.org"))); added.putAll(newProperty); entity.getProperties().putAll(newProperty); return added; @@ -237,7 +254,7 @@ private Map> prepareForRemove() { public void buildsInstanceFieldFromTypedAxiomValues() { final Map> properties = Generators.generateTypedProperties(); final Collection> axioms = createAxiomsForProperties(properties); - axioms.forEach(ax -> strategy.addValueFromAxiom(ax)); + axioms.forEach(ax -> strategy.addAxiomValue(ax)); strategy.buildInstanceFieldValue(entity); assertEquals(properties, entity.getProperties()); } @@ -247,16 +264,17 @@ private Collection> createAxiomsForProperties(Map> dat final Collection> axioms = new ArrayList<>(); for (Map.Entry> e : data.entrySet()) { axioms.addAll(e.getValue().stream() - .map(val -> new AxiomImpl<>(subject, Assertion.createPropertyAssertion(e.getKey(), false), - new Value<>(val))).collect(Collectors.toList())); + .map(val -> new AxiomImpl<>(subject, Assertion.createPropertyAssertion(e.getKey(), false), + new Value<>(val))).toList()); } return axioms; } @Test - public void buildInstanceFieldFromEmptyAxiomsLeavesItNull() { + public void buildInstanceFieldFromEmptyAxiomsLeavesSetsItToEmptyMap() { strategy.buildInstanceFieldValue(entity); - assertNull(entity.getProperties()); + assertNotNull(entity.getProperties()); + assertTrue(entity.getProperties().isEmpty()); } @SuppressWarnings("unchecked") @@ -271,6 +289,6 @@ public void buildInstanceFieldFromAxiomsWithIncompatibleValueTypeThrowsIllegalAr MetamodelMocks mocks = new MetamodelMocks(); final PropertiesFieldStrategy s = new PropertiesFieldStrategy<>(mocks.forOwlClassP().entityType(), spec, new EntityDescriptor(), mapperMock); - assertThrows(IllegalArgumentException.class, () -> s.addValueFromAxiom(axiom)); + assertThrows(IllegalArgumentException.class, () -> s.addAxiomValue(axiom)); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TypesFieldStrategyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TypesFieldStrategyTest.java index b48497a23..0cb244bd9 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TypesFieldStrategyTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/TypesFieldStrategyTest.java @@ -24,20 +24,41 @@ import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.TypesSpecification; -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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasItem; -import static org.junit.jupiter.api.Assertions.*; +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.assertTrue; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) public class TypesFieldStrategyTest { private static final URI IDENTIFIER = Generators.createIndividualIdentifier(); @@ -53,8 +74,6 @@ public class TypesFieldStrategyTest { @BeforeEach public void setUp() throws Exception { - MockitoAnnotations.openMocks(this); - this.mocks = new MetamodelMocks(); this.entityA = new OWLClassA(); entityA.setUri(IDENTIFIER); @@ -82,8 +101,8 @@ public void extractsTypesForPersist() throws Exception { final Set toAdd = OOMTestUtils.getTypesToAdd(gatherer); assertEquals(count, toAdd.size()); - verifyCollectionsAreEqual(entityA.getTypes(), toAdd); - assertNull(OOMTestUtils.getTypesToRemove(gatherer)); + assertEquals(entityA.getTypes(), toAdd.stream().map(URI::toString).collect(Collectors.toSet())); + assertThat(OOMTestUtils.getTypesToRemove(gatherer), empty()); } @Test @@ -102,16 +121,10 @@ public void extractsAddedTypes() throws Exception { final Set toAdd = OOMTestUtils.getTypesToAdd(gatherer); assertEquals(addedTypes.size(), toAdd.size()); - verifyCollectionsAreEqual(addedTypes, toAdd); + assertEquals(addedTypes, toAdd.stream().map(URI::toString).collect(Collectors.toSet())); assertTrue(OOMTestUtils.getTypesToRemove(gatherer).isEmpty()); } - private void verifyCollectionsAreEqual(Set expected, Set actual) { - for (URI type : actual) { - assertTrue(expected.contains(type.toString())); - } - } - private OWLClassA createOriginal() { final OWLClassA a = new OWLClassA(); a.setUri(IDENTIFIER); @@ -138,7 +151,7 @@ public void extractsRemovedTypes() throws Exception { final Set toRemove = OOMTestUtils.getTypesToRemove(gatherer); assertFalse(toRemove.isEmpty()); - verifyCollectionsAreEqual(removedTypes, toRemove); + assertEquals(removedTypes, toRemove.stream().map(URI::toString).collect(Collectors.toSet())); assertTrue(OOMTestUtils.getTypesToAdd(gatherer).isEmpty()); } @@ -172,8 +185,8 @@ public void extractsRemovedTypesWhenValueIsNull() throws Exception { final Set toRemove = OOMTestUtils.getTypesToRemove(gatherer); assertFalse(toRemove.isEmpty()); - verifyCollectionsAreEqual(original.getTypes(), toRemove); - assertNull(OOMTestUtils.getTypesToAdd(gatherer)); + assertEquals(toRemove.stream().map(URI::toString).collect(Collectors.toSet()), original.getTypes()); + assertThat(OOMTestUtils.getTypesToAdd(gatherer), empty()); } @Test @@ -193,10 +206,10 @@ public void extractsAddedAndRemovedTypes() throws Exception { final Set toRemove = OOMTestUtils.getTypesToRemove(gatherer); assertFalse(toRemove.isEmpty()); - verifyCollectionsAreEqual(removedTypes, toRemove); + assertEquals(removedTypes, toRemove.stream().map(URI::toString).collect(Collectors.toSet())); final Set toAdd = OOMTestUtils.getTypesToAdd(gatherer); assertEquals(addedTypes.size(), toAdd.size()); - verifyCollectionsAreEqual(addedTypes, toAdd); + assertEquals(addedTypes, toAdd.stream().map(URI::toString).collect(Collectors.toSet())); } @Test @@ -206,8 +219,8 @@ public void extractsNothingWhenThereAreNoTypesAndNoOriginal() throws Exception { when(mapperMock.getOriginalInstance(entityA)).thenReturn(null); strategy.buildAxiomValuesFromInstance(entityA, gatherer); - assertNull(OOMTestUtils.getTypesToRemove(gatherer)); - assertNull(OOMTestUtils.getTypesToAdd(gatherer)); + assertThat(OOMTestUtils.getTypesToRemove(gatherer), empty()); + assertThat(OOMTestUtils.getTypesToAdd(gatherer), empty()); } @Test @@ -219,8 +232,8 @@ public void extractsNothingWhenOriginalTypesAndCurrentTypesAreNull() throws Exce strategy.buildAxiomValuesFromInstance(entityA, gatherer); - assertNull(OOMTestUtils.getTypesToRemove(gatherer)); - assertNull(OOMTestUtils.getTypesToAdd(gatherer)); + assertThat(OOMTestUtils.getTypesToRemove(gatherer), empty()); + assertThat(OOMTestUtils.getTypesToAdd(gatherer), empty()); } @Test @@ -255,7 +268,7 @@ public void buildsStringBasedTypesFieldValueFromAxioms() { final TypesFieldStrategy strategy = strategy(mocks.forOwlClassA().entityType(), mocks.forOwlClassA().typesSpec()); final List> axioms = generateClassAssertionAxioms(OWLClassA.getClassIri()); - axioms.forEach(strategy::addValueFromAxiom); + axioms.forEach(strategy::addAxiomValue); final OWLClassA a = new OWLClassA(); strategy.buildInstanceFieldValue(a); @@ -280,7 +293,7 @@ public void buildsUriBasedTypesFieldValueFromAxioms() { final TypesFieldStrategy strategy = strategy(mocks.forOwlClassP().entityType(), mocks.forOwlClassP().types()); final List> axioms = generateClassAssertionAxioms(OWLClassP.getClassIri()); - axioms.forEach(strategy::addValueFromAxiom); + axioms.forEach(strategy::addAxiomValue); final OWLClassP p = new OWLClassP(); strategy.buildInstanceFieldValue(p); @@ -291,18 +304,19 @@ public void buildsUriBasedTypesFieldValueFromAxioms() { } @Test - public void buildInstanceFieldLeavesFieldNullWhenNoAxiomsAreLoaded() { + public void buildInstanceFieldSetsFieldValueToEmptySetWhenNoAxiomsAreLoaded() { final TypesFieldStrategy strategy = strategy(mocks.forOwlClassP().entityType(), mocks.forOwlClassP().types()); final List> axioms = Collections.singletonList( new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createClassAssertion(false), new Value<>(URI.create(OWLClassP.getClassIri())))); - axioms.forEach(strategy::addValueFromAxiom); + axioms.forEach(strategy::addAxiomValue); final OWLClassP p = new OWLClassP(); assertNull(p.getTypes()); strategy.buildInstanceFieldValue(p); - assertNull(p.getTypes()); + assertNotNull(p.getTypes()); + assertTrue(p.getTypes().isEmpty()); } @Test diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/metamodel/PolymorphicEntityTypeResolverTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/metamodel/PolymorphicEntityTypeResolverTest.java index 2ec4695af..5bb6c123f 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/metamodel/PolymorphicEntityTypeResolverTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/oom/metamodel/PolymorphicEntityTypeResolverTest.java @@ -25,7 +25,7 @@ import cz.cvut.kbss.jopa.model.IRI; import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.metamodel.*; -import cz.cvut.kbss.jopa.oom.exceptions.AmbiguousEntityTypeException; +import cz.cvut.kbss.jopa.oom.exception.AmbiguousEntityTypeException; import cz.cvut.kbss.ontodriver.model.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -186,8 +186,8 @@ public void determineActualEntityTypeTraversesOverMappedSuperclassInEntityHierar public void determineActualEntityTypeReturnsNullWhenMostSpecificTypeIsAbstract() { final String abstractTypeIri = Generators.createIndividualIdentifier().toString(); final IdentifiableEntityType etAbstract = spy( - new AbstractEntityType<>(AbstractSubtype.class.getSimpleName(), - AbstractSubtype.class, IRI.create(abstractTypeIri))); + new AbstractEntityType<>( + AbstractSubtype.class, IRI.create(abstractTypeIri))); doReturn(Collections.singleton(etR)).when(etAbstract).getSupertypes(); doReturn(Collections.singleton(etAbstract)).when(etR).getSubtypes(); @@ -202,8 +202,8 @@ private static abstract class AbstractSubtype extends OWLClassR { public void determineActualEntityTypeIsNotAmbiguousWhenOneOfTheTypesIsAbstract() { final String abstractTypeIri = Generators.createIndividualIdentifier().toString(); final IdentifiableEntityType etAbstract = spy( - new AbstractEntityType<>(AbstractSubtype.class.getSimpleName(), - AbstractSubtype.class, IRI.create(abstractTypeIri))); + new AbstractEntityType<>( + AbstractSubtype.class, IRI.create(abstractTypeIri))); doReturn(Collections.singleton(etR)).when(etAbstract).getSupertypes(); doReturn(Collections.singleton(etAbstract)).when(etR).getSubtypes(); final IdentifiableEntityType mostSpecificEtOne = generateEntityTypeSubtree(Generators.randomPositiveInt(5), etR); @@ -216,8 +216,8 @@ public void determineActualEntityTypeIsNotAmbiguousWhenOneOfTheTypesIsAbstract() public void determineActualEntityTypeSearchesForMoreSpecificTypeWhenRootTypeIsAbstract() { final String abstractTypeIri = Generators.createIndividualIdentifier().toString(); final IdentifiableEntityType etAbstract = spy( - new AbstractEntityType<>(AbstractSubtype.class.getSimpleName(), - AbstractSubtype.class, IRI.create(abstractTypeIri))); + new AbstractEntityType<>( + AbstractSubtype.class, IRI.create(abstractTypeIri))); doReturn(Collections.singleton(etS)).when(etAbstract).getSubtypes(); // We are abusing the type erasure here a little when(etS.getSupertypes()).thenReturn((Set) Collections.singleton(etAbstract)); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectListTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectListTest.java similarity index 88% rename from jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectListTest.java rename to jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectListTest.java index 348248b3f..a8bca7b87 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectListTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectListTest.java @@ -15,12 +15,12 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.adapters; +package cz.cvut.kbss.jopa.proxy.change; import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.OWLClassC; import cz.cvut.kbss.jopa.environment.utils.Generators; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,14 +30,27 @@ import java.lang.reflect.Field; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class IndirectListTest { +class ChangeTrackingIndirectListTest { private static List list; private static List backupList; @@ -45,9 +58,9 @@ class IndirectListTest { private static Field ownerField; @Mock - private UnitOfWorkImpl uow; + private UnitOfWork uow; - private IndirectList target; + private ChangeTrackingIndirectList target; @BeforeAll static void setUpBeforeClass() throws Exception { @@ -61,7 +74,7 @@ static void setUpBeforeClass() throws Exception { @BeforeEach void setUp() { - target = new IndirectList<>(owner, ownerField, uow, list); + target = new ChangeTrackingIndirectList<>(owner, ownerField, uow, list); list.clear(); list.addAll(backupList); owner.setReferencedList(target); @@ -69,12 +82,12 @@ void setUp() { @Test void constructorThrowsNullPointerForNullReferencedList() { - assertThrows(NullPointerException.class, () -> new IndirectList<>(owner, ownerField, uow, null)); + assertThrows(NullPointerException.class, () -> new ChangeTrackingIndirectList<>(owner, ownerField, uow, null)); } @Test void constructorThrowsNullPointerForNullUnitOfWork() { - assertThrows(NullPointerException.class, () -> new IndirectList<>(owner, ownerField, null, list)); + assertThrows(NullPointerException.class, () -> new ChangeTrackingIndirectList<>(owner, ownerField, null, list)); } @Test @@ -285,7 +298,8 @@ void testListIteratorRemove() { @Test void equalsWorksForTwoIndirectLists() { - final IndirectList other = new IndirectList<>(owner, ownerField, uow, list); + final ChangeTrackingIndirectList + other = new ChangeTrackingIndirectList<>(owner, ownerField, uow, list); assertEquals(target, other); } @@ -307,7 +321,7 @@ void testContainsAll() { @Test void sublistReturnsAnotherIndirectList() { final List result = target.subList(0, target.size() / 2); - assertTrue(result instanceof IndirectList); + assertInstanceOf(ChangeTrackingIndirectList.class, result); } @Test diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectMapTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMapTest.java similarity index 80% rename from jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectMapTest.java rename to jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMapTest.java index 0e8297f26..5271a9695 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectMapTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMapTest.java @@ -15,11 +15,11 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.adapters; +package cz.cvut.kbss.jopa.proxy.change; import cz.cvut.kbss.jopa.environment.OWLClassB; import cz.cvut.kbss.jopa.environment.utils.Generators; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -33,22 +33,26 @@ import java.util.Map; import java.util.Set; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class IndirectMapTest { +class ChangeTrackingIndirectMapTest { private static OWLClassB owner; private static Field ownerField; private static Map> backupMap; @Mock - private UnitOfWorkImpl uow; + private UnitOfWork uow; private Map> map; - private IndirectMap> sut; + private ChangeTrackingIndirectMap> sut; @BeforeAll static void setUpBeforeClass() throws Exception { @@ -62,18 +66,18 @@ static void setUpBeforeClass() throws Exception { void setUp() { this.map = new HashMap<>(); map.putAll(backupMap); - this.sut = new IndirectMap<>(owner, ownerField, uow, map); + this.sut = new ChangeTrackingIndirectMap<>(owner, ownerField, uow, map); } @Test void testConstructorNullUoW() { - assertThrows(NullPointerException.class, () -> new IndirectMap<>(owner, ownerField, null, map)); + assertThrows(NullPointerException.class, () -> new ChangeTrackingIndirectMap<>(owner, ownerField, null, map)); } @Test void testConstructorNullMap() { - assertThrows(NullPointerException.class, () -> new IndirectMap<>(owner, ownerField, uow, null)); + assertThrows(NullPointerException.class, () -> new ChangeTrackingIndirectMap<>(owner, ownerField, uow, null)); } @Test @@ -116,7 +120,8 @@ void testClear() { @Test void equalsWorksForTwoIndirectMaps() { - final IndirectMap> other = new IndirectMap<>(owner, ownerField, uow, map); + final ChangeTrackingIndirectMap> + other = new ChangeTrackingIndirectMap<>(owner, ownerField, uow, map); assertEquals(sut, other); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectMultilingualStringTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMultilingualStringTest.java similarity index 81% rename from jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectMultilingualStringTest.java rename to jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMultilingualStringTest.java index e22b762b3..d027406ca 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectMultilingualStringTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMultilingualStringTest.java @@ -15,12 +15,12 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.adapters; +package cz.cvut.kbss.jopa.proxy.change; import cz.cvut.kbss.jopa.environment.OWLClassU; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.model.MultilingualString; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,12 +31,17 @@ import java.util.HashMap; import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class IndirectMultilingualStringTest { +class ChangeTrackingIndirectMultilingualStringTest { private MultilingualString referencedString; private Map translations; @@ -44,9 +49,9 @@ class IndirectMultilingualStringTest { private Field field; @Mock - private UnitOfWorkImpl uow; + private UnitOfWork uow; - private IndirectMultilingualString sut; + private ChangeTrackingIndirectMultilingualString sut; @BeforeEach void setUp() throws Exception { @@ -56,19 +61,19 @@ void setUp() throws Exception { this.referencedString = new MultilingualString(translations); this.owner = new OWLClassU(Generators.createIndividualIdentifier()); this.field = OWLClassU.getSingularStringAttField(); - this.sut = new IndirectMultilingualString(owner, field, uow, referencedString); + this.sut = new ChangeTrackingIndirectMultilingualString(owner, field, uow, referencedString); } @Test void constructorThrowsNullPointerForNullReferencedString() { assertThrows(NullPointerException.class, - () -> new IndirectMultilingualString(owner, field, uow, null)); + () -> new ChangeTrackingIndirectMultilingualString(owner, field, uow, null)); } @Test void constructorThrowsNullPointerForNullUnitOfWork() { assertThrows(NullPointerException.class, - () -> new IndirectMultilingualString(owner, field, null, referencedString)); + () -> new ChangeTrackingIndirectMultilingualString(owner, field, null, referencedString)); } @Test @@ -114,11 +119,11 @@ void readOperationsPropagateToReferencedInstance() { @Test void equalsWorksWithUnwrappedMultilingualString() { assertEquals(sut, referencedString); - assertEquals(sut, new IndirectMultilingualString(owner, field, uow, referencedString)); + assertEquals(sut, new ChangeTrackingIndirectMultilingualString(owner, field, uow, referencedString)); final MultilingualString another = new MultilingualString(translations); another.set("de", "der Bau"); assertNotEquals(sut, another); - assertNotEquals(sut, new IndirectMultilingualString(owner, field, uow, another)); + assertNotEquals(sut, new ChangeTrackingIndirectMultilingualString(owner, field, uow, another)); assertNotEquals(sut, "test"); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectSetTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectSetTest.java similarity index 83% rename from jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectSetTest.java rename to jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectSetTest.java index e1ed3ee22..50652e601 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/adapters/IndirectSetTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectSetTest.java @@ -15,12 +15,12 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. */ -package cz.cvut.kbss.jopa.adapters; +package cz.cvut.kbss.jopa.proxy.change; import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.OWLClassF; import cz.cvut.kbss.jopa.environment.utils.Generators; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -34,24 +34,29 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.IntStream; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class IndirectSetTest { +class ChangeTrackingIndirectSetTest { private static Set set; private static Set backupSet; private static OWLClassF owner; private static Field ownerField; - private IndirectSet target; + private ChangeTrackingIndirectSet target; @Mock - private UnitOfWorkImpl uow; + private UnitOfWork uow; @BeforeAll static void setUpBeforeClass() throws Exception { @@ -65,7 +70,7 @@ static void setUpBeforeClass() throws Exception { @BeforeEach void setUp() { - target = new IndirectSet<>(owner, ownerField, uow, set); + target = new ChangeTrackingIndirectSet<>(owner, ownerField, uow, set); set.clear(); set.addAll(backupSet); owner.setSimpleSet(target); @@ -73,12 +78,12 @@ void setUp() { @Test void testIndirectSetNullUoW() { - assertThrows(NullPointerException.class, () -> new IndirectSet<>(owner, ownerField, null, set)); + assertThrows(NullPointerException.class, () -> new ChangeTrackingIndirectSet<>(owner, ownerField, null, set)); } @Test void testIndirectSetNullReferencedSet() { - assertThrows(NullPointerException.class, () -> new IndirectSet<>(owner, ownerField, uow, null)); + assertThrows(NullPointerException.class, () -> new ChangeTrackingIndirectSet<>(owner, ownerField, uow, null)); } @Test @@ -147,8 +152,9 @@ void testRemove() { @Test void testAddAll() { when(uow.isInTransaction()).thenReturn(Boolean.TRUE); - final List toAdd = IntStream.range(0, 5).mapToObj(i -> Generators.generateOwlClassAInstance()) - .collect(Collectors.toList()); + final List toAdd = IntStream.range(0, 5) + .mapToObj(i -> Generators.generateOwlClassAInstance()) + .toList(); target.addAll(toAdd); verify(uow).attributeChanged(owner, ownerField); assertEquals(backupSet.size() + toAdd.size(), set.size()); @@ -200,7 +206,7 @@ void testClear() { @Test void equalsWorksForTwoIndirectSets() { - final IndirectSet other = new IndirectSet<>(owner, ownerField, uow, set); + final ChangeTrackingIndirectSet other = new ChangeTrackingIndirectSet<>(owner, ownerField, uow, set); assertEquals(target, other); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingCollectionProxyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingCollectionProxyTest.java new file mode 100644 index 000000000..dfce87e49 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingCollectionProxyTest.java @@ -0,0 +1,217 @@ +package cz.cvut.kbss.jopa.proxy.lazy; + +import cz.cvut.kbss.jopa.environment.OWLClassP; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.exception.LazyLoadingException; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +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 java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class LazyLoadingCollectionProxyTest { + + @Mock + private UnitOfWork uow; + + @Mock + private FieldSpecification> att; + + private List proxiedCollection; + + private OWLClassP entity; + + private LazyLoadingCollectionProxy, URI> sut; + + @BeforeEach + void setUp() { + this.proxiedCollection = spy(new ArrayList<>(List.of(Generators.createIndividualIdentifier(), Generators.createIndividualIdentifier()))); + this.entity = new OWLClassP(); + entity.setUri(Generators.createIndividualIdentifier()); + this.sut = new LazyLoadingListProxy<>(entity, att, uow); + } + + @Test + void triggerLazyLoadingThrowsLazyLoadingExceptionWhenNoPersistenceContextIsAvailable() { + this.sut = new LazyLoadingListProxy<>(entity, att, null); + assertThrows(LazyLoadingException.class, () -> sut.triggerLazyLoading()); + } + + @Test + void triggerLazyLoadingThrowsLazyLoadingExceptionWhenPersistenceContextIsNotActiveAnymore() { + when(uow.isActive()).thenReturn(false); + assertThrows(LazyLoadingException.class, () -> sut.triggerLazyLoading()); + } + + @Test + void sizeTriggersLazyLoadingAndReturnsLoadedCollectionSize() { + initLazyLoading(); + assertEquals(proxiedCollection.size(), sut.size()); + verify(uow).loadEntityField(entity, att); + // One call is in the assertion above, the other is the one we expect to be invoked by the SUT + verify(proxiedCollection, times(2)).size(); + } + + private void initLazyLoading() { + entity.setSimpleList((List) sut); + when(uow.isActive()).thenReturn(true); + when(uow.loadEntityField(entity, att)).thenAnswer(inv -> { + final OWLClassP owner = inv.getArgument(0); + owner.setSimpleList(proxiedCollection); + return proxiedCollection; + }); + } + + @Test + void isEmptyTriggersLazyLoadingAndReturnsLoadedCollectionIsEmptyResult() { + initLazyLoading(); + assertEquals(proxiedCollection.isEmpty(), sut.isEmpty()); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection, times(2)).isEmpty(); + } + + @Test + void containsTriggersLazyLoadingAndReturnsLoadedCollectionContainsResult() { + initLazyLoading(); + final URI arg = proxiedCollection.get(0); + assertEquals(proxiedCollection.contains(arg), sut.contains(arg)); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection, times(2)).contains(arg); + } + + @Test + void iteratorTriggersLazyLoadingAndReturnsLoadedCollectionIterator() { + initLazyLoading(); + final Iterator result = sut.iterator(); + assertNotNull(result); + final Iterator expected = proxiedCollection.iterator(); + while (result.hasNext()) { + assertTrue(expected.hasNext()); + assertEquals(expected.next(), result.next()); + } + assertFalse(expected.hasNext()); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection, times(2)).iterator(); + } + + @Test + void toArrayTriggersLazyLoadingAndReturnsLoadedCollectionToArrayResult() { + initLazyLoading(); + assertArrayEquals(proxiedCollection.toArray(), sut.toArray()); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection, times(2)).toArray(); + } + + @Test + void toTypedArrayTriggersLazyLoadingAndReturnsLoadedCollectionToTypedArrayResult() { + initLazyLoading(); + assertArrayEquals(proxiedCollection.toArray(new URI[] {}), sut.toArray(new URI[] {})); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection, times(2)).toArray(any(URI[].class)); + } + + @Test + void addTriggersLazyLoadingAddsElementToLoadedCollectionAndReturnsResult() { + initLazyLoading(); + final URI toAdd = Generators.createIndividualIdentifier(); + assertTrue(sut.add(toAdd)); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection).add(toAdd); + } + + @Test + void removeTriggersLazyLoadingRemovesElementFromLoadedCollectionAndReturnsResult() { + initLazyLoading(); + final URI toRemove = proxiedCollection.get(1); + assertTrue(sut.remove(toRemove)); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection).remove(toRemove); + } + + @Test + void containsAllTriggersLazyLoadingAndReturnsLoadedCollectionContainsAllResult() { + initLazyLoading(); + final Collection notContained = Set.of(Generators.createIndividualIdentifier(), proxiedCollection.get(0)); + assertEquals(proxiedCollection.containsAll(notContained), sut.containsAll(notContained)); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection, times(2)).containsAll(notContained); + } + + @Test + void addAllTriggersLazyLoadingAddsElementsToLoadedCollectionAndReturnsResult() { + initLazyLoading(); + final Collection toAdd = List.of(Generators.createIndividualIdentifier(), Generators.createIndividualIdentifier()); + assertTrue(sut.addAll(toAdd)); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection).addAll(toAdd); + } + + @Test + void removeAllTriggersLazyLoadingRemovesElementsAndReturnsResult() { + initLazyLoading(); + final Collection toRemove = new ArrayList<>(proxiedCollection); + assertTrue(sut.removeAll(toRemove)); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection).removeAll(toRemove); + } + + @Test + void retainAllTriggersLazyLoadingRetainsElementsAndReturnsResult() { + initLazyLoading(); + final Collection toRetain = List.of(proxiedCollection.get(0)); + assertTrue(sut.retainAll(toRetain)); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection).retainAll(toRetain); + } + + @Test + void clearTriggersLazyLoadingAndClearsLoadedCollection() { + initLazyLoading(); + sut.clear(); + verify(uow).loadEntityField(entity, att); + verify(proxiedCollection).clear(); + } + + @Test + void getLoadedValueReturnsLoadedValueAfterTriggeringLazyLoading() { + initLazyLoading(); + assertFalse(sut.isEmpty()); + assertTrue(sut.isLoaded()); + assertEquals(proxiedCollection, sut.getLoadedValue()); + } + + @Test + void triggerLazyLoadingMultipleTimesReturnsAlreadyLoadedValueAndDoesNotCallPersistenceContext() { + initLazyLoading(); + IntStream.range(0, 5).forEach(i -> assertEquals(proxiedCollection, sut.triggerLazyLoading())); + verify(uow, times(1)).loadEntityField(entity, att); + } + + @Test + void getLoadedThrowsIllegalStateExceptionWhenCalledBeforeLoading() { + assertThrows(IllegalStateException.class, () -> sut.getLoadedValue()); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingListProxyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingListProxyTest.java new file mode 100644 index 000000000..010cd941f --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingListProxyTest.java @@ -0,0 +1,154 @@ +package cz.cvut.kbss.jopa.proxy.lazy; + +import cz.cvut.kbss.jopa.environment.OWLClassP; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +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 java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; + +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.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class LazyLoadingListProxyTest { + + @Mock + private UnitOfWork uow; + + @Mock + private FieldSpecification> att; + + private List proxiedList; + + private OWLClassP entity; + + private LazyLoadingListProxy sut; + + @BeforeEach + void setUp() { + this.proxiedList = spy(new ArrayList<>(List.of(Generators.createIndividualIdentifier(), Generators.createIndividualIdentifier()))); + this.entity = new OWLClassP(); + entity.setUri(Generators.createIndividualIdentifier()); + this.sut = new LazyLoadingListProxy<>(entity, att, uow); + entity.setSimpleList(sut); + when(uow.isActive()).thenReturn(true); + when(uow.loadEntityField(entity, att)).thenAnswer(inv -> { + final OWLClassP owner = inv.getArgument(0); + owner.setSimpleList(proxiedList); + return proxiedList; + }); + } + + @Test + void addAllAtIndexTriggersLazyLoadingAddsElementsToLoadedListAndReturnsResult() { + final Collection toAdd = List.of(Generators.createIndividualIdentifier(), Generators.createIndividualIdentifier()); + final int index = proxiedList.size(); + assertTrue(sut.addAll(index, toAdd)); + verify(uow).loadEntityField(entity, att); + verify(proxiedList).addAll(index, toAdd); + } + + @Test + void getTriggersLazyLoadingAndReturnsLoadedListGetResult() { + final int index = 1; + assertEquals(proxiedList.get(index), sut.get(index)); + verify(uow).loadEntityField(entity, att); + verify(proxiedList, times(2)).get(index); + } + + @Test + void setTriggersLazyLoadingSetsValueInLoadedListAndReturnsResult() { + final URI toSet = Generators.createIndividualIdentifier(); + final int index = 0; + assertEquals(proxiedList.get(index), sut.set(index, toSet)); + verify(uow).loadEntityField(entity, att); + verify(proxiedList).set(index, toSet); + } + + @Test + void addAtIndexTriggersLazyLoadingAndAddsElementToLoadedList() { + final URI toAdd = Generators.createIndividualIdentifier(); + final int index = 1; + sut.add(index, toAdd); + verify(uow).loadEntityField(entity, att); + verify(proxiedList).add(index, toAdd); + } + + @Test + void removeAtIndexTriggersLazyLoadingRemovesElementFromLoadedListAndReturnsResult() { + final int index = 0; + assertEquals(proxiedList.get(index), sut.remove(index)); + verify(uow).loadEntityField(entity, att); + verify(proxiedList).remove(index); + } + + @Test + void indexOfTriggersLazyLoadingAndReturnsLoadedListIndexOfResult() { + final URI indexOfItem = proxiedList.get(1); + assertEquals(proxiedList.indexOf(indexOfItem), sut.indexOf(indexOfItem)); + verify(uow).loadEntityField(entity, att); + verify(proxiedList, times(2)).indexOf(indexOfItem); + } + + @Test + void lastIndexOfTriggersLazyLoadingAndReturnsLoadedListLastIndexOfResult() { + final URI lastIndexOfItem = proxiedList.get(1); + assertEquals(proxiedList.lastIndexOf(lastIndexOfItem), sut.lastIndexOf(lastIndexOfItem)); + verify(uow).loadEntityField(entity, att); + verify(proxiedList, times(2)).lastIndexOf(lastIndexOfItem); + } + + @Test + void listIteratorTriggersLazyLoadingAndReturnsLoadedListListIterator() { + final ListIterator result = sut.listIterator(); + assertNotNull(result); + final ListIterator expected = proxiedList.listIterator(); + while (result.hasNext()) { + assertTrue(expected.hasNext()); + assertEquals(expected.next(), result.next()); + } + assertFalse(expected.hasNext()); + verify(uow).loadEntityField(entity, att); + verify(proxiedList, times(2)).listIterator(); + } + + @Test + void listIteratorAtIndexTriggersLazyLoadingAndReturnsLoadedListListIterator() { + final int index = 1; + final ListIterator result = sut.listIterator(index); + assertNotNull(result); + final ListIterator expected = proxiedList.listIterator(index); + while (result.hasNext()) { + assertTrue(expected.hasNext()); + assertEquals(expected.next(), result.next()); + } + assertFalse(expected.hasNext()); + verify(uow).loadEntityField(entity, att); + verify(proxiedList, times(2)).listIterator(index); + } + + @Test + void subListTriggersLazyLoadingAndReturnsLoadedListSubListResult() { + final int start = 0; + final int end = 1; + assertEquals(proxiedList.subList(start, end), sut.subList(start, end)); + verify(uow).loadEntityField(entity, att); + verify(proxiedList, times(2)).subList(start, end); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingMapProxyTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingMapProxyTest.java new file mode 100644 index 000000000..3d54a9ea1 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/LazyLoadingMapProxyTest.java @@ -0,0 +1,187 @@ +package cz.cvut.kbss.jopa.proxy.lazy; + +import cz.cvut.kbss.jopa.environment.OWLClassB; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.exception.LazyLoadingException; +import cz.cvut.kbss.jopa.model.metamodel.PropertiesSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +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 java.util.Map; +import java.util.Set; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class LazyLoadingMapProxyTest { + + @Mock + private UnitOfWork uow; + + @Mock + private PropertiesSpecification>, String, String> fieldSpec; + + private Map> proxiedProperties; + + private OWLClassB entity; + + private LazyLoadingMapProxy> sut; + + @BeforeEach + void setUp() { + this.proxiedProperties = Generators.generateStringProperties(); + this.entity = new OWLClassB(Generators.createIndividualIdentifier()); + this.sut = new LazyLoadingMapProxy<>(entity, fieldSpec, uow); + } + + @Test + void triggerLazyLoadingThrowsLazyLoadingExceptionWhenNoPersistenceContextIsAvailable() { + this.sut = new LazyLoadingMapProxy<>(entity, fieldSpec, null); + assertThrows(LazyLoadingException.class, () -> sut.triggerLazyLoading()); + } + + @Test + void triggerLazyLoadingThrowsLazyLoadingExceptionWhenPersistenceContextIsNotActiveAnymore() { + when(uow.isActive()).thenReturn(false); + assertThrows(LazyLoadingException.class, () -> sut.triggerLazyLoading()); + } + + @Test + void sizeTriggersLazyLoadingAndReturnsLoadedMapSizeResult() { + initLazyLoading(); + assertEquals(proxiedProperties.size(), sut.size()); + verify(uow).loadEntityField(entity, fieldSpec); + } + + private void initLazyLoading() { + entity.setProperties(sut); + when(uow.isActive()).thenReturn(true); + when(uow.loadEntityField(entity, fieldSpec)).thenAnswer(inv -> { + final OWLClassB owner = inv.getArgument(0); + owner.setProperties(proxiedProperties); + return proxiedProperties; + }); + } + + @Test + void isEmptyTriggersLazyLoadingAndReturnsMapIsEmptyResult() { + initLazyLoading(); + assertEquals(proxiedProperties.isEmpty(), sut.isEmpty()); + verify(uow).loadEntityField(entity, fieldSpec); + } + + @Test + void containsKeyTriggersLazyLoadingAndReturnsMapContainsKeyResult() { + initLazyLoading(); + final String key = Generators.createIndividualIdentifier().toString(); + assertEquals(proxiedProperties.containsKey(key), sut.containsKey(key)); + verify(uow).loadEntityField(entity, fieldSpec); + } + + @Test + void containsValueTriggersLazyLoadingAndReturnsMapContainsValueResult() { + initLazyLoading(); + final Set value = proxiedProperties.values().iterator().next(); + assertEquals(proxiedProperties.containsValue(value), sut.containsValue(value)); + verify(uow).loadEntityField(entity, fieldSpec); + } + + @Test + void getTriggersLazyLoadingAndReturnsMapGetResult() { + initLazyLoading(); + final String key = proxiedProperties.keySet().iterator().next(); + assertEquals(proxiedProperties.get(key), sut.get(key)); + verify(uow).loadEntityField(entity, fieldSpec); + } + + @Test + void putTriggersLazyLoadingAndReturnsMapPutResult() { + initLazyLoading(); + final String key = Generators.createPropertyIdentifier().toString(); + final Set values = Set.of("1", "2", Generators.createIndividualIdentifier().toString()); + sut.put(key, values); + assertEquals(values, proxiedProperties.get(key)); + verify(uow).loadEntityField(entity, fieldSpec); + } + + @Test + void removeTriggersLazyLoadingAndReturnsMapRemoveResult() { + initLazyLoading(); + final String key = proxiedProperties.keySet().iterator().next(); + assertEquals(proxiedProperties.get(key), sut.remove(key)); + verify(uow).loadEntityField(entity, fieldSpec); + assertFalse(proxiedProperties.containsKey(key)); + } + + @Test + void putAllTriggersLazyLoadingAndPutsAllArgumentsToLoadedMap() { + initLazyLoading(); + final Map> toPut = Generators.generateStringProperties(2, 2); + sut.putAll(toPut); + verify(uow).loadEntityField(entity, fieldSpec); + toPut.forEach((key, value) -> { + assertTrue(proxiedProperties.containsKey(key)); + assertEquals(proxiedProperties.get(key), value); + }); + } + + @Test + void clearTriggersLazyLoadingAndClearsLoadedMap() { + initLazyLoading(); + sut.clear(); + verify(uow).loadEntityField(entity, fieldSpec); + assertTrue(proxiedProperties.isEmpty()); + } + + @Test + void keySetTriggersLazyLoadingAndReturnsMapKeySetResult() { + initLazyLoading(); + assertEquals(proxiedProperties.keySet(), sut.keySet()); + verify(uow).loadEntityField(entity, fieldSpec); + } + + @Test + void valuesTriggersLazyLoadingAndReturnsMapValues() { + initLazyLoading(); + assertEquals(proxiedProperties.values(), sut.values()); + verify(uow).loadEntityField(entity, fieldSpec); + } + + @Test + void entrySetTriggersLazyLoadingAndReturnsMapValues() { + initLazyLoading(); + assertEquals(proxiedProperties.entrySet(), sut.entrySet()); + verify(uow).loadEntityField(entity, fieldSpec); + } + + @Test + void getLoadedValueReturnsLoadedValueAfterTriggeringLazyLoading() { + initLazyLoading(); + assertFalse(sut.isEmpty()); + assertTrue(sut.isLoaded()); + assertEquals(proxiedProperties, sut.getLoadedValue()); + } + + @Test + void triggerLazyLoadingMultipleTimesReturnsAlreadyLoadedValueAndDoesNotCallPersistenceContext() { + initLazyLoading(); + IntStream.range(0, 5).forEach(i -> assertEquals(proxiedProperties, sut.triggerLazyLoading())); + verify(uow, times(1)).loadEntityField(entity, fieldSpec); + } + + @Test + void getLoadedThrowsIllegalStateExceptionWhenCalledBeforeLoading() { + assertThrows(IllegalStateException.class, () -> sut.getLoadedValue()); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGeneratorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGeneratorTest.java new file mode 100644 index 000000000..4de4586ec --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGeneratorTest.java @@ -0,0 +1,131 @@ +package cz.cvut.kbss.jopa.proxy.lazy.gen; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.stream.IntStream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class LazyLoadingEntityProxyGeneratorTest { + + @Mock + private UnitOfWork uow; + + @Mock + private FieldSpecification fieldSpec; + + private final OWLClassD owner = new OWLClassD(Generators.createIndividualIdentifier()); + + private final OWLClassA loaded = Generators.generateOwlClassAInstance(); + + private final LazyLoadingEntityProxyGenerator sut = new LazyLoadingEntityProxyGenerator(); + + @Test + void generateGeneratesProxyThatTriggersLazyLoadingForGetterAndReturnsLoadedInstance() throws Exception { + initLazyLoading(); + final Class resultCls = sut.generate(OWLClassA.class); + final OWLClassA proxy = resultCls.getDeclaredConstructor().newInstance(); + ((LazyLoadingProxyPropertyAccessor) proxy).setPersistenceContext(uow); + ((LazyLoadingProxyPropertyAccessor) proxy).setFieldSpec(fieldSpec); + ((LazyLoadingProxyPropertyAccessor) proxy).setOwner(owner); + assertInstanceOf(LazyLoadingEntityProxy.class, proxy); + owner.setOwlClassA(proxy); + assertEquals(loaded.getUri(), owner.getOwlClassA().getUri()); + assertEquals(loaded, owner.getOwlClassA()); + verify(uow).loadEntityField(owner, fieldSpec); + } + + private void initLazyLoading() { + doAnswer(inv -> { + final OWLClassD owner = inv.getArgument(0); + owner.setOwlClassA(loaded); + return loaded; + }).when(uow).loadEntityField(owner, fieldSpec); + when(uow.isActive()).thenReturn(true); + } + + @Test + void generateGeneratesProxyThatTriggersLazyLoadingForSetterAndSetsProvidedArgumentOnLoadedInstance() throws Exception { + initLazyLoading(); + final Class resultCls = sut.generate(OWLClassA.class); + final OWLClassA proxy = resultCls.getDeclaredConstructor().newInstance(); + ((LazyLoadingProxyPropertyAccessor) proxy).setPersistenceContext(uow); + ((LazyLoadingProxyPropertyAccessor) proxy).setFieldSpec(fieldSpec); + ((LazyLoadingProxyPropertyAccessor) proxy).setOwner(owner); + assertInstanceOf(LazyLoadingEntityProxy.class, proxy); + owner.setOwlClassA(proxy); + final String setValue = "Value set on lazily loaded reference"; + owner.getOwlClassA().setStringAttribute(setValue); + verify(uow).loadEntityField(owner, fieldSpec); + assertEquals(setValue, loaded.getStringAttribute()); + } + + @Test + void generateGeneratesProxyWithCommonToStringMethod() throws Exception { + when(fieldSpec.getName()).thenReturn("owlClassA"); + final Class resultCls = sut.generate(OWLClassA.class); + final OWLClassA proxy = resultCls.getDeclaredConstructor().newInstance(); + ((LazyLoadingProxyPropertyAccessor) proxy).setPersistenceContext(uow); + ((LazyLoadingProxyPropertyAccessor) proxy).setFieldSpec(fieldSpec); + ((LazyLoadingProxyPropertyAccessor) proxy).setOwner(owner); + assertInstanceOf(LazyLoadingEntityProxy.class, proxy); + final String result = proxy.toString(); + assertThat(result, containsString(OWLClassD.class.getSimpleName() + ".owlClassA")); + assertThat(result, containsString(resultCls.getSimpleName())); + assertEquals(((LazyLoadingEntityProxy) proxy).stringify(), result); + } + + @Test + void getLoadedValueOnGeneratedProxyReturnsLoadedValueAfterTriggeringLazyLoading() throws Exception { + initLazyLoading(); + final Class resultCls = sut.generate(OWLClassA.class); + final OWLClassA proxy = resultCls.getDeclaredConstructor().newInstance(); + ((LazyLoadingProxyPropertyAccessor) proxy).setPersistenceContext(uow); + ((LazyLoadingProxyPropertyAccessor) proxy).setFieldSpec(fieldSpec); + ((LazyLoadingProxyPropertyAccessor) proxy).setOwner(owner); + assertEquals(loaded.getStringAttribute(), proxy.getStringAttribute()); + assertTrue(((LazyLoadingEntityProxy) proxy).isLoaded()); + assertEquals(loaded, ((LazyLoadingEntityProxy) proxy).getLoadedValue()); + } + + @Test + void triggerLazyLoadingMultipleTimesOnGeneratedProxyReturnsAlreadyLoadedValueAndDoesNotCallPersistenceContext() throws Exception { + initLazyLoading(); + final Class resultCls = sut.generate(OWLClassA.class); + final OWLClassA proxy = resultCls.getDeclaredConstructor().newInstance(); + ((LazyLoadingProxyPropertyAccessor) proxy).setPersistenceContext(uow); + ((LazyLoadingProxyPropertyAccessor) proxy).setFieldSpec(fieldSpec); + ((LazyLoadingProxyPropertyAccessor) proxy).setOwner(owner); + IntStream.range(0, 5) + .forEach(i -> assertEquals(loaded, ((LazyLoadingEntityProxy) proxy).triggerLazyLoading())); + verify(uow, times(1)).loadEntityField(owner, fieldSpec); + } + + @Test + void getLoadedOnGeneratedProxyThrowsIllegalStateExceptionWhenCalledBeforeLoading() throws Exception { + final Class resultCls = sut.generate(OWLClassA.class); + final OWLClassA proxy = resultCls.getDeclaredConstructor().newInstance(); + ((LazyLoadingProxyPropertyAccessor) proxy).setPersistenceContext(uow); + ((LazyLoadingProxyPropertyAccessor) proxy).setFieldSpec(fieldSpec); + ((LazyLoadingProxyPropertyAccessor) proxy).setOwner(owner); + assertThrows(IllegalStateException.class, () -> ((LazyLoadingEntityProxy) proxy).getLoadedValue()); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderTest.java index b51a8a670..982d48739 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderTest.java @@ -20,13 +20,13 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import cz.cvut.kbss.jopa.model.query.criteria.CriteriaQuery; import cz.cvut.kbss.jopa.model.query.criteria.Order; import cz.cvut.kbss.jopa.model.query.criteria.Predicate; import cz.cvut.kbss.jopa.model.query.criteria.Root; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; import cz.cvut.kbss.jopa.sessions.MetamodelProvider; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -46,13 +46,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; - @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public class CriteriaBuilderTest { @Mock - private UnitOfWorkImpl uowMock; + private UnitOfWork uowMock; private static CriteriaBuilder f; @@ -71,7 +70,7 @@ void setUp() throws Exception { when(metamodel.getEntities()).thenReturn(Collections.emptySet()); when(mpp.isEntityType(any())).thenAnswer(inv -> metamodel.isEntityType(inv.getArgument(0))); - f = new CriteriaBuilderImpl(uowMock); + f = new CriteriaBuilderImpl(metamodel); } @Test @@ -86,7 +85,7 @@ public void testLiteralNullExpressionException() { @Test public void testParameterNamedNullExpressionException() { - Assertions.assertThrows(IllegalArgumentException.class, () -> f.parameter(null,"name")); + Assertions.assertThrows(IllegalArgumentException.class, () -> f.parameter(null, "name")); } @Test @@ -114,7 +113,7 @@ public void testOrderDescending() { public void testAndPredicateBooleanOperator() { CriteriaQuery query = f.createQuery(OWLClassA.class); Root root = query.from(OWLClassA.class); - Predicate predicate = f.and(f.equal(root.getAttr("stringAttribute"),"value")); + Predicate predicate = f.and(f.equal(root.getAttr("stringAttribute"), "value")); assertEquals(Predicate.BooleanOperator.AND, predicate.getOperator()); } @@ -122,7 +121,7 @@ public void testAndPredicateBooleanOperator() { public void testOrPredicateBooleanOperator() { CriteriaQuery query = f.createQuery(OWLClassA.class); Root root = query.from(OWLClassA.class); - Predicate predicate = f.or(f.equal(root.getAttr("stringAttribute"),"value")); + Predicate predicate = f.or(f.equal(root.getAttr("stringAttribute"), "value")); assertEquals(Predicate.BooleanOperator.OR, predicate.getOperator()); } @@ -130,7 +129,7 @@ public void testOrPredicateBooleanOperator() { public void testPredicateNegated() { CriteriaQuery query = f.createQuery(OWLClassA.class); Root root = query.from(OWLClassA.class); - Predicate predicate = f.not(f.equal(root.getAttr("stringAttribute"),"value")); + Predicate predicate = f.not(f.equal(root.getAttr("stringAttribute"), "value")); assertTrue(predicate.isNegated()); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaQueryTranslateQueryTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaQueryTranslateQueryTest.java index 730e5ba6a..a06b6fe11 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaQueryTranslateQueryTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaQueryTranslateQueryTest.java @@ -32,7 +32,7 @@ import cz.cvut.kbss.jopa.model.query.criteria.Predicate; import cz.cvut.kbss.jopa.model.query.criteria.Root; import cz.cvut.kbss.jopa.sessions.MetamodelProvider; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -57,7 +57,7 @@ public class CriteriaQueryTranslateQueryTest { @Mock - private UnitOfWorkImpl uowMock; + private UnitOfWork uowMock; private CriteriaBuilderImpl cb; private CriteriaParameterFiller criteriaParameterFiller; @@ -72,7 +72,7 @@ void setUp() throws Exception { when(metamodel.getEntities()).thenReturn(Collections.emptySet()); when(mpp.isEntityType(any())).thenAnswer(inv -> metamodel.isEntityType(inv.getArgument(0))); - this.cb = new CriteriaBuilderImpl(uowMock); + this.cb = new CriteriaBuilderImpl(metamodel); this.criteriaParameterFiller = new CriteriaParameterFiller(); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ConstructorResultMapperTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ConstructorResultMapperTest.java index bfa0bf35b..7bc9b481f 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ConstructorResultMapperTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ConstructorResultMapperTest.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.exception.SparqlResultMappingException; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.ontodriver.iteration.ResultRow; import cz.cvut.kbss.ontodriver.model.LangString; import org.junit.jupiter.api.Test; @@ -33,8 +33,13 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class ConstructorResultMapperTest { @@ -43,7 +48,7 @@ class ConstructorResultMapperTest { private ResultRow resultRow; @Mock - private UnitOfWorkImpl uowMock; + private UnitOfWork uowMock; @Test void mapRetrievesVariableValueAndUsesConstructorToCreateNewInstance() { diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/EntityResultMapperTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/EntityResultMapperTest.java index 418bcf8c5..fb475901a 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/EntityResultMapperTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/EntityResultMapperTest.java @@ -18,8 +18,11 @@ package cz.cvut.kbss.jopa.query.mapper; import cz.cvut.kbss.jopa.environment.OWLClassA; -import cz.cvut.kbss.jopa.model.metamodel.EntityType; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.ontodriver.iteration.ResultRow; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,10 +44,10 @@ class EntityResultMapperTest { private ResultRow resultRow; @Mock - private UnitOfWorkImpl uowMock; + private UnitOfWork uowMock; @Mock - private EntityType etMock; + private IdentifiableEntityType etMock; private EntityResultMapper mapper; @@ -52,21 +55,26 @@ class EntityResultMapperTest { void setUp() { this.mapper = new EntityResultMapper<>(etMock); when(etMock.getJavaType()).thenReturn(OWLClassA.class); + when(uowMock.getLoadStateRegistry()).thenReturn(new LoadStateDescriptorRegistry(Object::toString)); } @Test void mapCreatesNewInstanceOfTargetTypeAndRegistersItInUOW() { final OWLClassA clone = new OWLClassA(); - when(uowMock.registerExistingObject(any(), any(), any())).thenReturn(clone); + when(uowMock.registerExistingObject(any(), any(CloneRegistrationDescriptor.class))).thenReturn(clone); final OWLClassA result = mapper.map(resultRow, uowMock); assertNotNull(result); - verify(uowMock).registerExistingObject(any(), any(), any()); + verify(uowMock).registerExistingObject(any(), any(CloneRegistrationDescriptor.class)); } @Test void mapUsesFieldMappersToPopulateEntityFields() { + final FieldSpecification fs = mock(FieldSpecification.class); + when(fs.getDeclaringType()).thenReturn(etMock); final FieldResultMapper fOne = mock(FieldResultMapper.class); + when(fOne.getFieldSpecification()).thenReturn(fs); final FieldResultMapper fTwo = mock(FieldResultMapper.class); + when(fTwo.getFieldSpecification()).thenReturn(fs); mapper.addFieldMapper(fOne); mapper.addFieldMapper(fTwo); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/FieldResultMapperTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/FieldResultMapperTest.java index bd24aea25..a598c6ded 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/FieldResultMapperTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/FieldResultMapperTest.java @@ -38,8 +38,12 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class FieldResultMapperTest { diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ObjectPropertyFieldResultMapperTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ObjectPropertyFieldResultMapperTest.java index 52795369a..e6227b9e9 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ObjectPropertyFieldResultMapperTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ObjectPropertyFieldResultMapperTest.java @@ -36,7 +36,11 @@ import java.net.URI; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -65,12 +69,12 @@ void mapLoadsInstanceByIdentifierFromUnitOfWork() throws Exception { when(resultRow.getObject(fieldName)).thenReturn(identifier); final OWLClassA aInstance = Generators.generateOwlClassAInstance(); aInstance.setUri(identifier); - when(uowMock.readObject(eq(OWLClassA.class), eq(identifier), any())).thenReturn(aInstance); + when(uowMock.readObjectWithoutRegistration(eq(OWLClassA.class), eq(identifier), any())).thenReturn(aInstance); final OWLClassD target = new OWLClassD(); mapper.map(resultRow, target, uowMock); assertEquals(aInstance, target.getOwlClassA()); - verify(uowMock).readObject(OWLClassA.class, identifier, new EntityDescriptor()); + verify(uowMock).readObjectWithoutRegistration(OWLClassA.class, identifier, new EntityDescriptor()); } @Test @@ -87,4 +91,4 @@ void mapUsesValueDirectlyWhenFieldIsPlainIdentifier() throws Exception { assertEquals(value, target.getIndividualUri()); verify(uowMock, never()).readObject(any(), eq(value), any()); } -} \ No newline at end of file +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ResultRowMapperTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ResultRowMapperTest.java index 9d396af6b..98f2e6763 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ResultRowMapperTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ResultRowMapperTest.java @@ -19,7 +19,7 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.utils.Generators; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.ontodriver.iteration.ResultRow; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -43,7 +43,7 @@ class ResultRowMapperTest { private ResultRow resultRow; @Mock - private UnitOfWorkImpl uowMock; + private UnitOfWork uowMock; private final ResultRowMapper rowMapper = new ResultRowMapper("testMapping"); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ResultSetMappingProcessorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ResultSetMappingProcessorTest.java index c8f8eb940..a9aba0a33 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ResultSetMappingProcessorTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/ResultSetMappingProcessorTest.java @@ -179,7 +179,7 @@ public void buildMapperThrowsMappingExceptionWhenEntityMappingTargetClassIsNotEn final SparqlResultMappingException ex = assertThrows(SparqlResultMappingException.class, () -> processor.buildMapper(getMapping(WithEntityMapping.class))); assertEquals("Type " + OWLClassA.class + - " is not a known entity type and cannot be used as @EntityResult target class.", ex.getMessage()); + " is not a known instantiable entity type and cannot be used as @EntityResult target class.", ex.getMessage()); } @Test diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/VariableResultMapperTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/VariableResultMapperTest.java index 44c8ecf95..41c6f8a9b 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/VariableResultMapperTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/mapper/VariableResultMapperTest.java @@ -19,7 +19,7 @@ import cz.cvut.kbss.jopa.model.annotations.SparqlResultSetMapping; import cz.cvut.kbss.jopa.model.annotations.VariableResult; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.iteration.ResultRow; import cz.cvut.kbss.ontodriver.model.LangString; @@ -32,7 +32,9 @@ import java.time.OffsetDateTime; import java.util.Date; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,7 +47,7 @@ class VariableResultMapperTest { private ResultRow resultRow; @Mock - private UnitOfWorkImpl uowMock; + private UnitOfWork uowMock; @Test void mapReadsValueFromResultSetByVariableNameAndReturnsIt() throws OntoDriverException { diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/parameter/EntityParameterValueTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/parameter/EntityParameterValueTest.java index bf0cd12f6..4baf75f35 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/parameter/EntityParameterValueTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/parameter/EntityParameterValueTest.java @@ -21,7 +21,7 @@ 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.metamodel.Metamodel; +import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.sessions.MetamodelProvider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -37,7 +37,7 @@ class EntityParameterValueTest { private MetamodelProvider metamodelProvider; @Mock - private Metamodel metamodel; + private MetamodelImpl metamodel; @BeforeEach void setUp() throws Exception { diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/parameter/ParameterValueFactoryTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/parameter/ParameterValueFactoryTest.java index 76247449b..1bb000f43 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/parameter/ParameterValueFactoryTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/parameter/ParameterValueFactoryTest.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; -import cz.cvut.kbss.jopa.model.metamodel.Metamodel; +import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.sessions.MetamodelProvider; import cz.cvut.kbss.jopa.vocabulary.XSD; import org.junit.jupiter.api.Test; @@ -33,7 +33,15 @@ import java.net.URI; import java.net.URL; -import java.time.*; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collection; import java.util.Date; @@ -230,7 +238,7 @@ void createCollectionValueCreatesCollectionOfUrisForEntityElements() throws Exce when(metamodelProvider.isEntityType(any())).thenReturn(false); when(metamodelProvider.isEntityType(OWLClassA.class)).thenReturn(true); final MetamodelMocks mocks = new MetamodelMocks(); - final Metamodel metamodel = mock(Metamodel.class); + final MetamodelImpl metamodel = mock(MetamodelImpl.class); mocks.setMocks(metamodel); when(metamodelProvider.getMetamodel()).thenReturn(metamodel); final ParameterValue result = sut.create(values); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryFactoryTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryFactoryTest.java index 016c128c6..6175829c8 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryFactoryTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryFactoryTest.java @@ -26,7 +26,7 @@ import cz.cvut.kbss.jopa.query.ResultSetMappingManager; import cz.cvut.kbss.jopa.query.mapper.SparqlResultMapper; import cz.cvut.kbss.jopa.sessions.ConnectionWrapper; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -37,7 +37,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -48,7 +50,7 @@ public class SparqlQueryFactoryTest { private static final Class CLS = OWLClassA.class; @Mock - private UnitOfWorkImpl uowMock; + private UnitOfWork uowMock; @Mock private NamedQueryManager namedQueryManagerMock; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java similarity index 64% rename from jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkTest.java rename to jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java index abefdf5c0..9170d6c81 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java @@ -17,44 +17,41 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectMap; -import cz.cvut.kbss.jopa.adapters.IndirectMultilingualString; -import cz.cvut.kbss.jopa.adapters.IndirectSet; import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.OWLClassB; +import cz.cvut.kbss.jopa.environment.OWLClassC; import cz.cvut.kbss.jopa.environment.OWLClassD; -import cz.cvut.kbss.jopa.environment.OWLClassF; +import cz.cvut.kbss.jopa.environment.OWLClassE; import cz.cvut.kbss.jopa.environment.OWLClassL; -import cz.cvut.kbss.jopa.environment.OWLClassR; -import cz.cvut.kbss.jopa.environment.OWLClassU; import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.exception.IdentifierNotSetException; import cz.cvut.kbss.jopa.exceptions.CardinalityConstraintViolatedException; import cz.cvut.kbss.jopa.exceptions.EntityNotFoundException; -import cz.cvut.kbss.jopa.exceptions.InferredAttributeModifiedException; import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; -import cz.cvut.kbss.jopa.model.EntityManagerImpl.State; +import cz.cvut.kbss.jopa.model.EntityState; import cz.cvut.kbss.jopa.model.LoadState; -import cz.cvut.kbss.jopa.model.MultilingualString; import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraint; 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.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; +import cz.cvut.kbss.jopa.proxy.lazy.gen.LazyLoadingEntityProxyGenerator; +import cz.cvut.kbss.jopa.sessions.cache.Descriptors; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; +import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; -import cz.cvut.kbss.ontodriver.model.Assertion; -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.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.lang.annotation.Annotation; -import java.lang.reflect.Field; import java.net.URI; import java.util.ArrayList; import java.util.Collections; @@ -66,9 +63,8 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import static cz.cvut.kbss.jopa.environment.utils.ContainsSameEntities.containsSameEntities; import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -83,7 +79,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -91,51 +86,49 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class UnitOfWorkTest extends UnitOfWorkTestBase { - @BeforeEach - protected void setUp() throws Exception { - super.setUp(); - } +abstract class AbstractUnitOfWorkTestRunner extends UnitOfWorkTestBase { @Test - void testReadObjectNullPrimaryKey() { + void readObjectWithNullIdentifierArgumentThrowsNullPointerException() { assertThrows(NullPointerException.class, () -> uow.readObject(entityA.getClass(), null, descriptor)); - verify(cacheManagerMock, never()).get(any(), any(), any()); + verify(serverSessionStub.getLiveObjectCache(), never()).get(any(), any(), any()); } @Test - void testReadObjectNullClass() { + void readObjectWithNullClassThrowsNullPointerException() { assertThrows(NullPointerException.class, () -> uow.readObject(null, entityB.getUri(), descriptor)); - verify(cacheManagerMock, never()).get(any(), any(), any()); + verify(serverSessionStub.getLiveObjectCache(), never()).get(any(), any(), any()); } @Test - void testReadObjectNullContext() { + void readObjectWithNullDescriptorThrowsNullPointerException() { assertThrows(NullPointerException.class, () -> uow.readObject(entityA.getClass(), entityA.getUri(), null)); - verify(cacheManagerMock, never()).get(any(), any(), any()); + verify(serverSessionStub.getLiveObjectCache(), never()).get(any(), any(), any()); } @Test - void testReadObjectFromOntology() { + void readObjectLoadsObjectFromStorage() { when(storageMock.find(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor))) .thenReturn(entityA); + defaultLoadStateDescriptor(entityA); OWLClassA res = uow.readObject(OWLClassA.class, entityA.getUri(), descriptor); assertNotNull(res); assertEquals(entityA.getUri(), res.getUri()); + verify(storageMock).find(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor)); } @Test - void testReadObjectJustPersisted() { + void readNewlyRegisteredObjectReturnsIt() { uow.registerNewObject(entityA, descriptor); assertTrue(uow.contains(entityA)); final OWLClassA res = uow.readObject(OWLClassA.class, entityA.getUri(), descriptor); - assertNotNull(res); assertSame(entityA, res); } @Test void readAlreadyManagedObjectReturnsTheManagedOne() { + defaultLoadStateDescriptor(entityA); final OWLClassA clone = (OWLClassA) uow.registerExistingObject(entityA, descriptor); assertNotNull(clone); final OWLClassA res = uow.readObject(OWLClassA.class, entityA.getUri(), descriptor); @@ -145,14 +138,14 @@ void readAlreadyManagedObjectReturnsTheManagedOne() { } @Test - void testCalculateNewObjects() { + void commitAddsNewObjectsToCache() { uow.registerNewObject(entityA, descriptor); uow.registerNewObject(entityB, descriptor); uow.registerNewObject(entityD, descriptor); uow.commit(); ArgumentCaptor pks = ArgumentCaptor.forClass(Object.class); - verify(cacheManagerMock, times(3)).add(pks.capture(), any(Object.class), eq(descriptor)); + verify(serverSessionStub.getLiveObjectCache(), times(3)).add(pks.capture(), any(Object.class), any(Descriptors.class)); final Set uris = pks.getAllValues().stream().map(pk -> URI.create(pk.toString())).collect( Collectors.toSet()); assertTrue(uris.contains(entityA.getUri())); @@ -161,24 +154,37 @@ void testCalculateNewObjects() { } @Test - void testCalculateDeletedObjects() { + void commitRemovesRemovedObjectsFromStorage() { + defaultLoadStateDescriptor(entityA, entityB); final Object toRemove = uow.registerExistingObject(entityA, descriptor); uow.registerExistingObject(entityB, descriptor); uow.removeObject(toRemove); uow.commit(); - verify(cacheManagerMock).evict(OWLClassA.class, entityA.getUri(), CONTEXT_URI); verify(storageMock).remove(entityA.getUri(), entityA.getClass(), descriptor); } @Test - void testCalculateModificationsObjectProperty() throws Exception { + void commitEvictsRemovedObjectsFromCache() { + defaultLoadStateDescriptor(entityA, entityB); + final Object toRemove = uow.registerExistingObject(entityA, descriptor); + uow.registerExistingObject(entityB, descriptor); + uow.removeObject(toRemove); + uow.commit(); + + verify(serverSessionStub.getLiveObjectCache()).evict(OWLClassA.class, entityA.getUri(), CONTEXT_URI); + } + + @Test + void commitAddsNewlyAddedReferenceToObjectToCache() throws Exception { when(transactionMock.isActive()).thenReturn(Boolean.TRUE); final OWLClassD d = new OWLClassD(); d.setUri(URI.create("http://tempD")); final OWLClassA a = new OWLClassA(); a.setUri(URI.create("http://oldA")); d.setOwlClassA(a); + defaultLoadStateDescriptor(d); + defaultLoadStateDescriptor(a); final OWLClassD clone = (OWLClassD) uow.registerExistingObject(d, descriptor); final OWLClassA newA = new OWLClassA(); newA.setUri(URI.create("http://newA")); @@ -189,29 +195,26 @@ void testCalculateModificationsObjectProperty() throws Exception { uow.commit(); assertEquals(d.getOwlClassA().getUri(), newA.getUri()); - verify(cacheManagerMock).add(eq(newA.getUri()), any(Object.class), eq(descriptor)); + verify(serverSessionStub.getLiveObjectCache()).add(eq(newA.getUri()), any(Object.class), any(Descriptors.class)); } @Test void testCalculateModificationsDataProperty() throws Exception { when(transactionMock.isActive()).thenReturn(Boolean.TRUE); - final OWLClassA newA = new OWLClassA(); - newA.setUri(URI.create("http://newA")); - newA.setStringAttribute("somestring"); - final OWLClassA clone = (OWLClassA) uow.registerExistingObject(newA, descriptor); - // Trigger change, otherwise we would have to stub - // OWLAPIPersistenceProvider's emfs and server session - uow.setHasChanges(); + final OWLClassA original = Generators.generateOwlClassAInstance(); + defaultLoadStateDescriptor(original); + final OWLClassA clone = (OWLClassA) uow.registerExistingObject(original, descriptor); final String newStr = "newStr"; clone.setStringAttribute(newStr); uow.attributeChanged(clone, OWLClassA.getStrAttField()); uow.commit(); - assertEquals(newStr, newA.getStringAttribute()); + assertEquals(newStr, original.getStringAttribute()); } @Test - void testContains() { + void containsReturnsTrueForRegisteredExistingObject() { + defaultLoadStateDescriptor(entityA); OWLClassA res = (OWLClassA) uow.registerExistingObject(entityA, descriptor); assertNotNull(res); assertTrue(uow.contains(res)); @@ -219,36 +222,35 @@ void testContains() { @Test void testGetState() { - assertEquals(State.NOT_MANAGED, uow.getState(entityA)); + assertEquals(EntityState.NOT_MANAGED, uow.getState(entityA)); + defaultLoadStateDescriptor(entityA); OWLClassA toRemove = (OWLClassA) uow.registerExistingObject(entityA, descriptor); - assertEquals(State.MANAGED, uow.getState(toRemove)); + assertEquals(EntityState.MANAGED, uow.getState(toRemove)); uow.removeObject(toRemove); - assertEquals(State.REMOVED, uow.getState(toRemove)); - final OWLClassA stateTest = new OWLClassA(); - final URI pk = URI.create("http://stateTest"); - stateTest.setUri(pk); + assertEquals(EntityState.REMOVED, uow.getState(toRemove)); + final OWLClassA stateTest = Generators.generateOwlClassAInstance(); uow.registerNewObject(stateTest, descriptor); - assertEquals(State.MANAGED_NEW, uow.getState(stateTest)); + assertEquals(EntityState.MANAGED_NEW, uow.getState(stateTest)); } @Test void testGetStateWithDescriptor() { - assertEquals(State.NOT_MANAGED, uow.getState(entityA, descriptor)); + assertEquals(EntityState.NOT_MANAGED, uow.getState(entityA, descriptor)); + defaultLoadStateDescriptor(entityA); OWLClassA toRemove = (OWLClassA) uow.registerExistingObject(entityA, descriptor); - assertEquals(State.MANAGED, uow.getState(toRemove, descriptor)); + assertEquals(EntityState.MANAGED, uow.getState(toRemove, descriptor)); uow.removeObject(toRemove); - assertEquals(State.REMOVED, uow.getState(toRemove, descriptor)); - final OWLClassA stateTest = new OWLClassA(); - final URI pk = URI.create("http://stateTest"); - stateTest.setUri(pk); + assertEquals(EntityState.REMOVED, uow.getState(toRemove, descriptor)); + final OWLClassA stateTest = Generators.generateOwlClassAInstance(); uow.registerNewObject(stateTest, descriptor); - assertEquals(State.MANAGED_NEW, uow.getState(stateTest, descriptor)); + assertEquals(EntityState.MANAGED_NEW, uow.getState(stateTest, descriptor)); } @Test - void testGetOriginal() { + void getOriginalReturnsOriginalRegisteredByReadObject() { when(storageMock.find(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor))).thenReturn( entityA); + defaultLoadStateDescriptor(entityA); OWLClassA tO = uow.readObject(OWLClassA.class, entityA.getUri(), descriptor); assertNotNull(tO); OWLClassA origOne = (OWLClassA) uow.getOriginal(tO); @@ -258,7 +260,7 @@ void testGetOriginal() { } @Test - void testGetOriginalNull() { + void getOriginalWithNullArgumentReturnsNull() { assertNull(uow.getOriginal(null)); } @@ -266,6 +268,7 @@ void testGetOriginalNull() { void getManagedOriginalReturnsManagedOriginalInstance() { when(storageMock.find(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor))).thenReturn( entityA); + defaultLoadStateDescriptor(entityA); uow.readObject(OWLClassA.class, entityA.getUri(), descriptor); final OWLClassA res = uow.getManagedOriginal(OWLClassA.class, entityA.getUri(), descriptor); @@ -277,6 +280,7 @@ void getManagedOriginalReturnsManagedOriginalInstance() { void getManagedOriginalForDifferentContextReturnsNull() { when(storageMock.find(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor))).thenReturn( entityA); + defaultLoadStateDescriptor(entityA); uow.readObject(OWLClassA.class, entityA.getUri(), descriptor); final EntityDescriptor differentContext = new EntityDescriptor(URI.create("http://differentContext")); @@ -289,35 +293,39 @@ void getManagedOriginalForUnknownIdentifierReturnsNull() { } @Test - void testIsObjectNew() { - final OWLClassA testNew = new OWLClassA(); - final URI pk = URI.create("http://testNewOne"); - testNew.setUri(pk); + void isObjectNewReturnsTrueForNewlyRegisteredObject() { + final OWLClassA testNew = Generators.generateOwlClassAInstance(); uow.registerNewObject(testNew, descriptor); assertTrue(uow.isObjectNew(testNew)); - verify(storageMock).persist(pk, testNew, descriptor); } @Test - void testIsObjectNewWithNullAndManaged() { + void isObjectNewReturnsFalseForNullArgument() { assertFalse(uow.isObjectNew(null)); + } + + @Test + void isObjectNewReturnsFalseForRegisteredExistingObject() { + defaultLoadStateDescriptor(entityA); OWLClassA managed = (OWLClassA) uow.registerExistingObject(entityA, descriptor); assertFalse(uow.isObjectNew(managed)); } @Test - void testIsObjectManaged() { + void isObjectManagedReturnsTrueForRegisteredExistingObject() { + defaultLoadStateDescriptor(entityA); OWLClassA managed = (OWLClassA) uow.registerExistingObject(entityA, descriptor); assertTrue(uow.isObjectManaged(managed)); } @Test - void testIsObjectManagerNull() { + void isObjectManagedThrowsNullPointerExceptionForNullArgument() { assertThrows(NullPointerException.class, () -> uow.isObjectManaged(null)); } @Test - void testRegisterExistingObject() { + void registerExistingObjectReturnsRegisteredClone() { + defaultLoadStateDescriptor(entityB); OWLClassB clone = (OWLClassB) uow.registerExistingObject(entityB, descriptor); assertNotNull(clone); assertEquals(entityB.getUri(), clone.getUri()); @@ -330,7 +338,8 @@ void testRegisterExistingObject() { * tests the second branch of the register method. */ @Test - void testRegisterExistingObjectTwice() { + void registerExistingObjectTwiceReturnsSameClone() { + defaultLoadStateDescriptor(entityB); OWLClassB clone = (OWLClassB) uow.registerExistingObject(entityB, descriptor); assertNotNull(clone); assertEquals(entityB.getUri(), clone.getUri()); @@ -339,30 +348,39 @@ void testRegisterExistingObjectTwice() { } @Test - void testRemoveObjectFromCache() { + void removeObjectFromCacheEvictsObjectFromCacheManager() { uow.removeObjectFromCache(entityB, descriptor.getSingleContext().orElse(null)); - verify(cacheManagerMock).evict(OWLClassB.class, entityB.getUri(), descriptor.getSingleContext().orElse(null)); + verify(serverSessionStub.getLiveObjectCache()).evict(OWLClassB.class, entityB.getUri(), descriptor.getSingleContext() + .orElse(null)); } @Test - void testRegisterNewObject() { - final OWLClassA newOne = new OWLClassA(); - final URI pk = URI.create("http://newEntity"); - newOne.setUri(pk); - newOne.setStringAttribute("stringAttributeOne"); + void registerNewObjectGeneratesIdentifierWhenInstancesDoesNotHaveOne() { + final OWLClassE entity = new OWLClassE(); + entity.setStringAttribute("Test value"); + final URI id = Generators.createIndividualIdentifier(); + when(storageMock.generateIdentifier(any(EntityType.class))).thenReturn(id); + uow.registerNewObject(entity, descriptor); + assertEquals(id, entity.getUri()); + verify(storageMock).generateIdentifier(metamodelMocks.forOwlClassE().entityType()); + } + + @Test + void registerNewObjectAddsArgumentToPersistenceContext() { + final OWLClassA newOne = Generators.generateOwlClassAInstance(); uow.registerNewObject(newOne, descriptor); assertTrue(uow.contains(newOne)); - assertEquals(State.MANAGED_NEW, uow.getState(newOne)); - verify(storageMock).persist(pk, newOne, descriptor); + assertEquals(EntityState.MANAGED_NEW, uow.getState(newOne)); + verify(storageMock, never()).persist(newOne.getUri(), newOne, descriptor); } @Test - void testRegisterNewObjectNull() { + void registerNewObjectThrowsNullPointerExceptionForNullArgument() { assertThrows(NullPointerException.class, () -> uow.registerNewObject(null, descriptor)); } @Test - void testRegisterNewObjectNullDescriptor() { + void registerNewObjectThrowsNullPointerExceptionForNullDescriptor() { assertThrows(NullPointerException.class, () -> uow.registerNewObject(entityA, null)); } @@ -374,7 +392,7 @@ void registerNewObjectThrowsIdentifierNotSetExceptionWhenIdentifierIsNullAndNotG } @Test - void testReleaseUnitOfWork() { + void releaseUnitOfWorkClosesStorageAndMakesUoWInactive() { assertTrue(uow.isActive()); uow.release(); assertFalse(uow.isActive()); @@ -382,34 +400,24 @@ void testReleaseUnitOfWork() { } @Test - void removeObjectPutsExistingObjectIntoDeletedCacheAndRemovesItFromRepository() { - final OWLClassB toRemove = (OWLClassB) uow.registerExistingObject(entityB, descriptor); - uow.removeObject(toRemove); - assertFalse(uow.contains(toRemove)); - assertEquals(State.REMOVED, uow.getState(toRemove)); - verify(storageMock).remove(entityB.getUri(), entityB.getClass(), descriptor); - } - - @Test - void testRemoveNewObject() { - final OWLClassB newOne = new OWLClassB(); - final URI pk = URI.create("http://testObject"); - newOne.setUri(pk); + void removeObjectRemovesNewlyRegisteredObjectFromPersistenceContext() { + final OWLClassB newOne = new OWLClassB(Generators.createIndividualIdentifier()); newOne.setStringAttribute("strAtt"); - this.uow.registerNewObject(newOne, descriptor); + uow.registerNewObject(newOne, descriptor); assertTrue(uow.contains(newOne)); - // Now try to remove it + uow.removeObject(newOne); assertFalse(uow.contains(newOne)); } @Test - void testRemoveObjectNotRegistered() { + void removeNotRegisteredObjectThrowsIllegalArgumentException() { assertThrows(IllegalArgumentException.class, () -> uow.removeObject(entityA)); } @Test - void testUnregisterObject() { + void unregisterRegisteredExistingObjectRemovesItFromPersistenceContext() { + defaultLoadStateDescriptor(entityA); final OWLClassA managed = (OWLClassA) uow.registerExistingObject(entityA, descriptor); assertTrue(uow.contains(managed)); uow.unregisterObject(managed); @@ -418,22 +426,23 @@ void testUnregisterObject() { @Test void unregisterObjectRemovesItFromCloneBuilderCache() { + defaultLoadStateDescriptor(entityA); final OWLClassA managed = (OWLClassA) uow.registerExistingObject(entityA, descriptor); uow.unregisterObject(managed); verify(cloneBuilder).removeVisited(entityA, descriptor); } @Test - void testCommitInactive() { + void commitInactiveUoWThrowsIllegalStateException() { uow.release(); assertThrows(IllegalStateException.class, () -> uow.commit()); } @Test - void testRollback() { + void rollbackRollsBackStorageChangesAndRemovesObjectsFromPersistenceContext() { uow.registerNewObject(entityA, descriptor); + defaultLoadStateDescriptor(entityB); final Object clone = uow.registerExistingObject(entityB, descriptor); - verify(storageMock).persist(entityA.getUri(), entityA, descriptor); assertTrue(uow.contains(entityA)); assertTrue(uow.contains(clone)); @@ -444,64 +453,49 @@ void testRollback() { } @Test - void testRollbackInactive() { + void rollbackInactiveUoWThrowsIllegalStateException() { uow.release(); assertThrows(IllegalStateException.class, () -> uow.rollback()); } @Test - void testCommitFailed() { - doThrow(OWLPersistenceException.class).when(storageMock).commit(); - - assertThrows(OWLPersistenceException.class, () -> uow.commit()); - verify(emMock).removeCurrentPersistenceContext(); - } - - @Test - void testClearCacheAfterCommit() { - uow.registerNewObject(entityA, descriptor); - final Object clone = uow.registerExistingObject(entityB, descriptor); - verify(storageMock).persist(entityA.getUri(), entityA, descriptor); - assertTrue(uow.contains(entityA)); - assertTrue(uow.contains(clone)); - uow.setShouldClearAfterCommit(true); - uow.commit(); - - verify(cacheManagerMock).evictAll(); - } - - @Test - void loadFieldLoadsLiteralValueAttribute() throws Exception { + void loadFieldLoadsLiteralValueAttribute() { final OWLClassB b = new OWLClassB(); b.setUri(URI.create("http://bUri")); final Map> props = Collections .singletonMap(Vocabulary.p_m_IntegerSet, Collections.singleton("12345")); + defaultLoadStateDescriptor(b); + uow.getLoadStateRegistry().get(b) + .setLoaded(metamodelMocks.forOwlClassB().propertiesSpec(), LoadState.NOT_LOADED); final OWLClassB clone = (OWLClassB) uow.registerExistingObject(b, descriptor); - final Field propsField = OWLClassB.getPropertiesField(); doAnswer(invocation -> { final FieldSpecification f = (FieldSpecification) invocation.getArguments()[1]; EntityPropertiesUtils.setFieldValue(f.getJavaField(), invocation.getArguments()[0], props); return null; }).when(storageMock).loadFieldValue(clone, metamodelMocks.forOwlClassB().propertiesSpec(), descriptor); - uow.loadEntityField(clone, propsField); + uow.loadEntityField(clone, metamodelMocks.forOwlClassB().propertiesSpec()); assertNotNull(clone.getProperties()); verify(storageMock).loadFieldValue(clone, metamodelMocks.forOwlClassB().propertiesSpec(), descriptor); } @Test - void loadFieldLoadsManagedTypeAttribute() throws Exception { + void loadFieldLoadsManagedTypeAttribute() { final OWLClassL original = new OWLClassL(Generators.createIndividualIdentifier()); + final LoadStateDescriptor loadStateDescriptor = LoadStateDescriptorFactory.createNotLoaded(original, metamodelMocks.forOwlClassL() + .entityType()); + uow.getLoadStateRegistry().put(original, loadStateDescriptor); + when(metamodelMock.getLazyLoadingProxy(OWLClassA.class)).thenReturn((Class) new LazyLoadingEntityProxyGenerator().generate(OWLClassA.class)); final OWLClassL clone = (OWLClassL) uow.registerExistingObject(original, descriptor); - final Field toLoad = OWLClassL.getSetField(); doAnswer(invocation -> { final FieldSpecification f = (FieldSpecification) invocation.getArguments()[1]; EntityPropertiesUtils.setFieldValue(f.getJavaField(), invocation.getArguments()[0], Collections.singleton(entityA)); return null; }).when(storageMock) .loadFieldValue(eq(clone), eq(metamodelMocks.forOwlClassL().setAttribute()), eq(descriptor)); + defaultLoadStateDescriptor(entityA); - uow.loadEntityField(clone, toLoad); + uow.loadEntityField(clone, metamodelMocks.forOwlClassL().setAttribute()); verify(storageMock).loadFieldValue(clone, metamodelMocks.forOwlClassL().setAttribute(), descriptor); assertNotNull(clone.getSet()); assertEquals(1, clone.getSet().size()); @@ -511,16 +505,19 @@ void loadFieldLoadsManagedTypeAttribute() throws Exception { } @Test - void findOfObjectAlreadyManagedAsLazilyLoadedValueReturnSameObject() throws Exception { + void findOfObjectAlreadyManagedAsLazilyLoadedValueReturnSameObject() { final OWLClassL original = new OWLClassL(Generators.createIndividualIdentifier()); + defaultLoadStateDescriptor(original); + uow.getLoadStateRegistry().get(original) + .setLoaded(metamodelMocks.forOwlClassL().setAttribute(), LoadState.NOT_LOADED); final OWLClassL clone = (OWLClassL) uow.registerExistingObject(original, descriptor); - final Field toLoad = OWLClassL.getSetField(); doAnswer(invocation -> { final FieldSpecification f = (FieldSpecification) invocation.getArguments()[1]; EntityPropertiesUtils.setFieldValue(f.getJavaField(), invocation.getArguments()[0], Collections.singleton(entityA)); return null; }).when(storageMock).loadFieldValue(clone, metamodelMocks.forOwlClassL().setAttribute(), descriptor); - uow.loadEntityField(clone, toLoad); + defaultLoadStateDescriptor(entityA); + uow.loadEntityField(clone, metamodelMocks.forOwlClassL().setAttribute()); assertNotNull(clone.getSet()); final OWLClassA res = uow.readObject(OWLClassA.class, entityA.getUri(), descriptor); @@ -529,42 +526,16 @@ void findOfObjectAlreadyManagedAsLazilyLoadedValueReturnSameObject() throws Exce } @Test - void testLoadFieldValueNotRegistered() { - assertThrows(OWLPersistenceException.class, () -> uow.loadEntityField(entityB, OWLClassB.getStrAttField())); + void loadEntityFieldOnUnmanagedObjectThrowsOwlPersistenceException() { + assertThrows(OWLPersistenceException.class, () -> uow.loadEntityField(entityB, metamodelMocks.forOwlClassB() + .stringAttribute())); verify(storageMock, never()).loadFieldValue(any(OWLClassB.class), - eq(metamodelMocks.forOwlClassB().stringAttribute()), - eq(descriptor)); + eq(metamodelMocks.forOwlClassB().stringAttribute()), + eq(descriptor)); } @Test - void testAttributeChanged() throws Exception { - when(transactionMock.isActive()).thenReturn(Boolean.TRUE); - final OWLClassA clone = (OWLClassA) uow.registerExistingObject(entityA, descriptor); - final Field strField = OWLClassA.getStrAttField(); - - uow.attributeChanged(clone, strField); - verify(storageMock).merge(clone, metamodelMocks.forOwlClassA().stringAttribute(), descriptor); - } - - @Test - void testAttributeChangedNotRegistered() throws Exception { - when(transactionMock.isActive()).thenReturn(Boolean.TRUE); - final Field strField = OWLClassA.getStrAttField(); - assertThrows(OWLPersistenceException.class, () -> uow.attributeChanged(entityA, strField)); - verify(storageMock, never()).merge(any(OWLClassA.class), eq(metamodelMocks.forOwlClassA().stringAttribute()), - eq(descriptor)); - } - - @Test - void testAttributeChangedOutsideTransaction() throws Exception { - final Field strField = OWLClassA.getStrAttField(); - assertThrows(IllegalStateException.class, () -> uow.attributeChanged(entityA, strField)); - verify(storageMock, never()).merge(any(OWLClassA.class), eq(metamodelMocks.forOwlClassA().stringAttribute()), - eq(descriptor)); - } - - @Test - void testIsConsistent() { + void isConsistentCallsStorageConsistencyCheck() { when(storageMock.isConsistent(CONTEXT_URI)).thenReturn(Boolean.TRUE); final boolean res = uow.isConsistent(CONTEXT_URI); assertTrue(res); @@ -572,12 +543,11 @@ void testIsConsistent() { } @Test - void testGetContexts() { + void getContextsRetrievesContextsFromStorage() { final List contexts = new ArrayList<>(1); contexts.add(CONTEXT_URI); when(storageMock.getContexts()).thenReturn(contexts); final List res = uow.getContexts(); - assertSame(contexts, res); assertEquals(contexts, res); verify(storageMock).getContexts(); } @@ -599,9 +569,12 @@ void throwsCardinalityViolationExceptionWhenMinimumCardinalityIsViolatedOnCommit when(transactionMock.isActive()).thenReturn(Boolean.TRUE); final List lst = new ArrayList<>(); for (int i = 0; i < 5; i++) { - lst.add(Generators.generateOwlClassAInstance()); + final OWLClassA a = Generators.generateOwlClassAInstance(); + lst.add(a); + defaultLoadStateDescriptor(a); } entityL.setSimpleList(lst); + defaultLoadStateDescriptor(entityL); final OWLClassL clone = (OWLClassL) uow.registerExistingObject(entityL, descriptor); clone.getSimpleList().clear(); uow.attributeChanged(clone, OWLClassL.getSimpleListField()); @@ -614,9 +587,12 @@ void icValidationPassesOnCommitWhenConstraintsAreViolatedAndThenFixedDuringTrans when(transactionMock.isActive()).thenReturn(Boolean.TRUE); final List lst = new ArrayList<>(); for (int i = 0; i < 5; i++) { - lst.add(Generators.generateOwlClassAInstance()); + final OWLClassA a = Generators.generateOwlClassAInstance(); + lst.add(a); + defaultLoadStateDescriptor(a); } entityL.setSimpleList(lst); + defaultLoadStateDescriptor(entityL); final OWLClassL clone = (OWLClassL) uow.registerExistingObject(entityL, descriptor); clone.setSimpleList(Collections.emptyList()); uow.attributeChanged(clone, OWLClassL.getSimpleListField()); @@ -631,44 +607,28 @@ void icValidationPassesOnCommitWhenConstraintsAreViolatedAndThenFixedDuringTrans } @Test - void clearCleansUpPersistenceContext() throws Exception { + void clearCleansUpPersistenceContext() { final OWLClassD d = new OWLClassD(); d.setUri(URI.create("http://dUri")); + defaultLoadStateDescriptor(d); uow.registerExistingObject(d, descriptor); final OWLClassB newOne = new OWLClassB(); final URI pk = URI.create("http://testObject"); newOne.setUri(pk); + defaultLoadStateDescriptor(newOne); uow.registerNewObject(newOne, descriptor); + defaultLoadStateDescriptor(entityA, entityB); final Object toRemove = uow.registerExistingObject(entityA, descriptor); uow.registerExistingObject(entityB, descriptor); uow.removeObject(toRemove); uow.clear(); - assertTrue(getMap("cloneToOriginals") == null || getMap("cloneToOriginals").isEmpty()); - assertTrue(getMap("keysToClones") == null || getMap("keysToClones").isEmpty()); - assertTrue(getMap("deletedObjects") == null || getMap("deletedObjects").isEmpty()); - assertTrue(getMap("newObjectsCloneToOriginal") == null || getMap("newObjectsCloneToOriginal").isEmpty()); - assertTrue(getMap("newObjectsKeyToClone") == null || getMap("newObjectsKeyToClone").isEmpty()); - assertFalse(getBoolean("hasChanges")); - assertFalse(getBoolean("hasNew")); - assertFalse(getBoolean("hasDeleted")); - } - - private Map getMap(String fieldName) throws Exception { - final Field field = uow.getClass().getDeclaredField(fieldName); - if (!field.canAccess(uow)) { - field.setAccessible(true); - } - return (Map) field.get(uow); - } - - - private boolean getBoolean(String fieldName) throws Exception { - final Field field = uow.getClass().getDeclaredField(fieldName); - if (!field.canAccess(uow)) { - field.setAccessible(true); - } - return (boolean) field.get(uow); + assertTrue(uow.cloneToOriginals.isEmpty()); + assertTrue(uow.keysToClones.isEmpty()); + assertTrue(uow.deletedObjects.isEmpty()); + assertTrue(uow.newObjectsCloneToOriginal.isEmpty()); + assertTrue(uow.newObjectsKeyToClone.isEmpty()); + assertFalse(uow.hasChanges()); } @Test @@ -676,59 +636,39 @@ void unwrapReturnsItselfWhenClassMatches() { assertSame(uow, uow.unwrap(UnitOfWork.class)); } - @Test - void releaseRemovesIndirectCollectionsFromManagedEntities() { - when(storageMock.find(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor, false))) - .thenReturn(entityA); - final OWLClassA result = uow.readObject(OWLClassA.class, entityA.getUri(), descriptor); - assertNotNull(result); - assertTrue(result.getTypes() instanceof IndirectSet); - uow.release(); - assertFalse(result.getTypes() instanceof IndirectSet); - } - @Test void rollbackDetachesAllManagedEntities() { when(storageMock.find(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor, false))) .thenReturn(entityA); + defaultLoadStateDescriptor(entityA); final OWLClassA result = uow.readObject(OWLClassA.class, entityA.getUri(), descriptor); entityB.setProperties(new HashMap<>()); uow.registerNewObject(entityB, descriptor); - assertTrue(result.getTypes() instanceof IndirectSet); - assertTrue(entityB.getProperties() instanceof IndirectMap); uow.rollback(); - assertFalse(result.getTypes() instanceof IndirectSet); - assertFalse(entityB.getProperties() instanceof IndirectMap); assertFalse(uow.contains(result)); assertFalse(uow.contains(entityB)); } - @Test - void registerReplacesAlsoInheritedCollectionInstancesWithIndirectVersions() { - final OWLClassR entityR = new OWLClassR(Generators.createIndividualIdentifier()); - entityR.setTypes(Generators.generateTypes(5)); - when(storageMock.find(new LoadingParameters<>(OWLClassR.class, entityR.getUri(), descriptor))) - .thenReturn(entityR); - final OWLClassR clone = uow.readObject(OWLClassR.class, entityR.getUri(), descriptor); - assertTrue(clone.getTypes() instanceof IndirectSet); - } - @Test void commitPutsIntoCacheInstanceMergedAsDetachedDuringTransaction() { final OWLClassA original = new OWLClassA(entityA.getUri()); original.setStringAttribute("originalStringAttribute"); when(storageMock.contains(entityA.getUri(), OWLClassA.class, descriptor)).thenReturn(true); when(storageMock.find(any())).thenReturn(original); + final LoadStateDescriptor loadStateDescriptor = LoadStateDescriptorFactory.createAllLoaded(original, metamodelMocks.forOwlClassA() + .entityType()); + uow.loadStateRegistry.put(original, loadStateDescriptor); final OWLClassA merged = uow.mergeDetached(entityA, descriptor); assertNotNull(merged); assertEquals(entityA.getStringAttribute(), merged.getStringAttribute()); uow.commit(); - verify(cacheManagerMock).add(entityA.getUri(), original, descriptor); + verify(serverSessionStub.getLiveObjectCache()).add(entityA.getUri(), original, new Descriptors(descriptor, loadStateDescriptor)); } @Test void clearResetsCloneBuilder() { + defaultLoadStateDescriptor(entityA); uow.registerExistingObject(entityA, descriptor); uow.clear(); verify(cloneBuilder).reset(); @@ -737,14 +677,16 @@ void clearResetsCloneBuilder() { @Test void registerExistingObjectInvokesPostCloneListeners() { final Consumer plVerifier = mock(Consumer.class); - final Object result = uow.registerExistingObject(entityA, descriptor, Collections.singletonList(plVerifier)); + defaultLoadStateDescriptor(entityA); + final Object result = uow.registerExistingObject(entityA, new CloneRegistrationDescriptor(descriptor).postCloneHandlers(List.of(plVerifier))); verify(plVerifier).accept(result); } @Test void registerExistingObjectPassesPostCloneListenersToCloneBuilder() { + defaultLoadStateDescriptor(entityA); final Consumer plVerifier = mock(Consumer.class); - uow.registerExistingObject(entityA, descriptor, Collections.singletonList(plVerifier)); + uow.registerExistingObject(entityA, new CloneRegistrationDescriptor(descriptor).postCloneHandlers(List.of(plVerifier))); final ArgumentCaptor captor = ArgumentCaptor.forClass(CloneConfiguration.class); verify(cloneBuilder).buildClone(eq(entityA), captor.capture()); assertTrue(captor.getValue().getPostRegister().contains(plVerifier)); @@ -753,28 +695,31 @@ void registerExistingObjectPassesPostCloneListenersToCloneBuilder() { @Test void refreshThrowsIllegalArgumentForNonManagedInstance() { final IllegalArgumentException result = assertThrows(IllegalArgumentException.class, - () -> uow.refreshObject( - Generators.generateOwlClassAInstance())); + () -> uow.refreshObject( + Generators.generateOwlClassAInstance())); assertEquals("Object not managed by this persistence context.", result.getMessage()); } @Test void refreshThrowsIllegalArgumentForRemovedInstance() { + defaultLoadStateDescriptor(entityA); final Object a = uow.registerExistingObject(entityA, descriptor); uow.removeObject(a); final IllegalArgumentException result = assertThrows(IllegalArgumentException.class, - () -> uow.refreshObject(a)); + () -> uow.refreshObject(a)); assertEquals("Object not managed by this persistence context.", result.getMessage()); } @Test void refreshAcquiresNewConnectionToGetAccessToNonTransactionalEntityState() { + defaultLoadStateDescriptor(entityA); final OWLClassA a = (OWLClassA) uow.registerExistingObject(entityA, descriptor); a.setStringAttribute("updatedString"); final OWLClassA original = new OWLClassA(entityA.getUri()); original.setStringAttribute(entityA.getStringAttribute()); original.setTypes(new HashSet<>(entityA.getTypes())); when(storageMock.find(any())).thenReturn(original); + defaultLoadStateDescriptor(original); uow.refreshObject(a); // First invocation is when UoW is instantiated verify(serverSessionStub, times(2)).acquireConnection(); @@ -782,6 +727,7 @@ void refreshAcquiresNewConnectionToGetAccessToNonTransactionalEntityState() { @Test void refreshLoadsInstanceFromRepositoryAndOverwritesFieldChanges() { + defaultLoadStateDescriptor(entityA); final OWLClassA a = (OWLClassA) uow.registerExistingObject(entityA, descriptor); a.setStringAttribute("updatedString"); final OWLClassA original = new OWLClassA(entityA.getUri()); @@ -790,6 +736,7 @@ void refreshLoadsInstanceFromRepositoryAndOverwritesFieldChanges() { final LoadingParameters loadingParams = new LoadingParameters<>(OWLClassA.class, a.getUri(), descriptor, true); loadingParams.bypassCache(); + defaultLoadStateDescriptor(original); when(storageMock.find(loadingParams)).thenReturn(original); uow.refreshObject(a); assertEquals(entityA.getStringAttribute(), a.getStringAttribute()); @@ -798,13 +745,16 @@ void refreshLoadsInstanceFromRepositoryAndOverwritesFieldChanges() { @Test void refreshOverwritesObjectPropertyChanges() { + defaultLoadStateDescriptor(entityD, entityA); final OWLClassD d = (OWLClassD) uow.registerExistingObject(entityD, descriptor); final OWLClassA origAClone = d.getOwlClassA(); final OWLClassA differentA = Generators.generateOwlClassAInstance(); + defaultLoadStateDescriptor(differentA); final OWLClassA diffAClone = (OWLClassA) uow.registerExistingObject(differentA, descriptor); d.setOwlClassA(diffAClone); final OWLClassD original = new OWLClassD(d.getUri()); original.setOwlClassA(entityA); + defaultLoadStateDescriptor(original); final LoadingParameters loadingParams = new LoadingParameters<>(OWLClassD.class, d.getUri(), descriptor, true); loadingParams.bypassCache(); @@ -818,6 +768,7 @@ void refreshOverwritesObjectPropertyChanges() { @Test void refreshSetsUpdatesCloneMappingForRefreshedInstance() { + defaultLoadStateDescriptor(entityD, entityA); final OWLClassD d = (OWLClassD) uow.registerExistingObject(entityD, descriptor); final OWLClassA differentA = Generators.generateOwlClassAInstance(); d.setOwlClassA(differentA); @@ -827,6 +778,7 @@ void refreshSetsUpdatesCloneMappingForRefreshedInstance() { new LoadingParameters<>(OWLClassD.class, d.getUri(), descriptor, true); loadingParams.bypassCache(); when(storageMock.find(loadingParams)).thenReturn(original); + defaultLoadStateDescriptor(original); uow.refreshObject(d); assertEquals(original, uow.getOriginal(d)); @@ -834,6 +786,7 @@ void refreshSetsUpdatesCloneMappingForRefreshedInstance() { @Test void refreshThrowsEntityNotFoundForNonExistentEntity() { + defaultLoadStateDescriptor(entityD, entityA); final OWLClassD d = (OWLClassD) uow.registerExistingObject(entityD, descriptor); final LoadingParameters loadingParams = new LoadingParameters<>(OWLClassD.class, d.getUri(), descriptor, true); @@ -844,26 +797,10 @@ void refreshThrowsEntityNotFoundForNonExistentEntity() { assertThat(result.getMessage(), containsString(d + " no longer exists in the repository")); } - @Test - void refreshCancelsObjectChangesInUnitOfWorkChangeSet() throws Exception { - when(transactionMock.isActive()).thenReturn(true); - final OWLClassA a = (OWLClassA) uow.registerExistingObject(entityA, descriptor); - a.setStringAttribute("updatedString"); - uow.attributeChanged(a, OWLClassA.getStrAttField()); - final OWLClassA original = new OWLClassA(entityA.getUri()); - original.setStringAttribute(entityA.getStringAttribute()); - original.setTypes(new HashSet<>(entityA.getTypes())); - when(storageMock.find(any())).thenReturn(original); - final UnitOfWorkChangeSet uowChangeSet = uow.getUowChangeSet(); - assertNotNull(uowChangeSet.getExistingObjectChanges(entityA)); - uow.refreshObject(a); - assertNull(uowChangeSet.getExistingObjectChanges(entityA)); - assertNull(uowChangeSet.getExistingObjectChanges(original)); - } - @Test void refreshOverwritesChangesSentToRepository() { when(transactionMock.isActive()).thenReturn(true); + defaultLoadStateDescriptor(entityA); final OWLClassA a = (OWLClassA) uow.registerExistingObject(entityA, descriptor); a.setStringAttribute("updatedString"); final OWLClassA original = new OWLClassA(entityA.getUri()); @@ -871,12 +808,14 @@ void refreshOverwritesChangesSentToRepository() { original.setTypes(new HashSet<>(entityA.getTypes())); Mockito.reset(storageMock); when(storageMock.find(any())).thenReturn(original); + defaultLoadStateDescriptor(original); uow.refreshObject(a); verify(storageMock).merge(eq(a), eq(metamodelMocks.forOwlClassA().stringAttribute()), any(Descriptor.class)); } @Test void restoreDeletedRegistersObjectAgain() { + defaultLoadStateDescriptor(entityA); final OWLClassA a = (OWLClassA) uow.registerExistingObject(entityA, descriptor); uow.removeObject(a); @@ -887,6 +826,7 @@ void restoreDeletedRegistersObjectAgain() { @Test void restoreDeletedReinsertsObjectIntoRepository() { + defaultLoadStateDescriptor(entityA); final OWLClassA a = (OWLClassA) uow.registerExistingObject(entityA, descriptor); uow.removeObject(a); uow.restoreRemovedObject(a); @@ -896,40 +836,18 @@ void restoreDeletedReinsertsObjectIntoRepository() { @Test void commitDetachesPersistedInstance() { uow.registerNewObject(entityA, descriptor); - assertTrue(entityA.getTypes() instanceof IndirectSet); assertTrue(uow.contains(entityA)); uow.commit(); assertFalse(uow.contains(entityA)); } - @Test - void commitReplacesIndirectCollectionsWithRegularOnesInDetachedInstances() { - uow.registerNewObject(entityA, descriptor); - assertTrue(entityA.getTypes() instanceof IndirectSet); - uow.commit(); - assertFalse(entityA.getTypes() instanceof IndirectSet); - } - - @Test - void detachReplacesInheritedIndirectCollectionWithRegularOne() { - final OWLClassR entityR = new OWLClassR(Generators.createIndividualIdentifier()); - entityR.setName("test"); - final Set types = Generators.generateTypes(3); - entityR.setTypes(types); - uow.registerNewObject(entityR, descriptor); - assertTrue(entityR.getTypes() instanceof IndirectSet); - assertEquals(types, entityR.getTypes()); - uow.commit(); - assertFalse(entityR.getTypes() instanceof IndirectSet); - assertEquals(types, entityR.getTypes()); - } - @Test void commitEvictsInferredClassesFromCache() { + defaultLoadStateDescriptor(entityA); uow.registerExistingObject(entityA, descriptor); uow.registerNewObject(entityB, descriptor); uow.commit(); - verify(cacheManagerMock).evictInferredObjects(); + verify(serverSessionStub.getLiveObjectCache()).evictInferredObjects(); } @Test @@ -947,12 +865,14 @@ void isLoadedByAttributeReturnsLoadedForAttributesOfNewlyRegisteredInstance() th @Test void isLoadedReturnsLoadedForRegisteredExistingObject() { + defaultLoadStateDescriptor(entityA); final OWLClassA a = (OWLClassA) uow.registerExistingObject(entityA, descriptor); assertEquals(LoadState.LOADED, uow.isLoaded(a)); } @Test void isLoadedByAttributeReturnsLoadedForAttributesOfRegisteredExistingObject() throws Exception { + defaultLoadStateDescriptor(entityA); final OWLClassA a = (OWLClassA) uow.registerExistingObject(entityA, descriptor); assertEquals(LoadState.LOADED, uow.isLoaded(a, OWLClassA.getStrAttField().getName())); assertEquals(LoadState.LOADED, uow.isLoaded(a, OWLClassA.getTypesField().getName())); @@ -969,13 +889,18 @@ void isLoadedByAttributeReturnsUnknownForAttributeOfUnregisteredObject() throws } @Test - void isLoadedByAttributeReturnsUnknownForNullValuedLazilyLoadedAttribute() throws Exception { + void isLoadedByAttributeReturnsNotLoadedForNotLoadedLazilyLoadedAttribute() throws Exception { + final LoadStateDescriptor loadStateDescriptor = LoadStateDescriptorFactory.createNotLoaded(entityL, metamodelMocks.forOwlClassL() + .entityType()); + uow.getLoadStateRegistry().put(entityL, loadStateDescriptor); + when(metamodelMock.getLazyLoadingProxy(OWLClassA.class)).thenReturn((Class) new LazyLoadingEntityProxyGenerator().generate(OWLClassA.class)); final OWLClassL instance = (OWLClassL) uow.registerExistingObject(entityL, descriptor); - assertEquals(LoadState.UNKNOWN, uow.isLoaded(instance, OWLClassL.getSetField().getName())); + assertEquals(LoadState.NOT_LOADED, uow.isLoaded(instance, OWLClassL.getSetField().getName())); } @Test void isLoadedByAttributeReturnsLoadedForNonNullValuedLazilyLoadedAttribute() throws Exception { + defaultLoadStateDescriptor(entityL, entityA); entityL.setSet(Collections.singleton(entityA)); final OWLClassL instance = (OWLClassL) uow.registerExistingObject(entityL, descriptor); assertEquals(LoadState.LOADED, uow.isLoaded(instance, OWLClassL.getSetField().getName())); @@ -983,75 +908,35 @@ void isLoadedByAttributeReturnsLoadedForNonNullValuedLazilyLoadedAttribute() thr @Test void loadEntityFieldCausesLoadStateOfLazilyLoadedAttributeToBeSetToLoaded() throws Exception { + final LoadStateDescriptor loadStateDescriptor = LoadStateDescriptorFactory.createNotLoaded(entityL, metamodelMocks.forOwlClassL() + .entityType()); + uow.getLoadStateRegistry().put(entityL, loadStateDescriptor); + when(metamodelMock.getLazyLoadingProxy(OWLClassA.class)).thenReturn((Class) new LazyLoadingEntityProxyGenerator().generate(OWLClassA.class)); final OWLClassL instance = (OWLClassL) uow.registerExistingObject(entityL, descriptor); - assertEquals(LoadState.UNKNOWN, uow.isLoaded(instance, OWLClassL.getSetField().getName())); doAnswer(inv -> { final OWLClassL inst = inv.getArgument(0); inst.setSet(Collections.singleton(entityA)); return null; }).when(storageMock).loadFieldValue(eq(instance), eq(metamodelMocks.forOwlClassL().setAttribute()), any()); - uow.loadEntityField(instance, OWLClassL.getSetField()); - - assertEquals(LoadState.LOADED, uow.isLoaded(instance, OWLClassL.getSetField().getName())); - } - - @Test - void loadEntityFieldCausesLoadStateOfLazilyLoadedAttributeToBeSetToLoadedEvenIfValueIsNull() throws Exception { - final OWLClassL instance = (OWLClassL) uow.registerExistingObject(entityL, descriptor); - assertEquals(LoadState.UNKNOWN, uow.isLoaded(instance, OWLClassL.getSetField().getName())); - // Do nothing when load field is triggered - uow.loadEntityField(instance, OWLClassL.getSetField()); - - assertEquals(LoadState.LOADED, uow.isLoaded(instance, OWLClassL.getSetField().getName())); - } - - @Test - void attributeChangedSetsAttributeLoadStatusToLoaded() throws Exception { - when(transactionMock.isActive()).thenReturn(Boolean.TRUE); - final OWLClassL instance = (OWLClassL) uow.registerExistingObject(entityL, descriptor); - assertEquals(LoadState.UNKNOWN, uow.isLoaded(instance, OWLClassL.getSetField().getName())); - instance.setSet(Collections.singleton(entityA)); - uow.attributeChanged(instance, OWLClassL.getSetField()); + defaultLoadStateDescriptor(entityA); + uow.loadEntityField(instance, metamodelMocks.forOwlClassL().setAttribute()); assertEquals(LoadState.LOADED, uow.isLoaded(instance, OWLClassL.getSetField().getName())); } - @Test - void loadEntityFieldDoesNotInvokeLoadFromRepositoryForNullAttributeWhenItsStateIsLoaded() throws Exception { - when(transactionMock.isActive()).thenReturn(Boolean.TRUE); - final OWLClassL instance = (OWLClassL) uow.registerExistingObject(entityL, descriptor); - assertEquals(LoadState.UNKNOWN, uow.isLoaded(instance, OWLClassL.getSetField().getName())); - uow.attributeChanged(instance, OWLClassL.getSetField()); - assertEquals(LoadState.LOADED, uow.isLoaded(instance, OWLClassL.getSetField().getName())); - uow.loadEntityField(instance, OWLClassL.getSetField()); - verify(storageMock, never()).loadFieldValue(eq(instance), eq(metamodelMocks.forOwlClassL().setAttribute()), - any(Descriptor.class)); - } - - @Test - void changesToRemovedObjectAreIgnoredOnCommit() throws Exception { - when(transactionMock.isActive()).thenReturn(Boolean.TRUE); - final OWLClassA instance = (OWLClassA) uow.registerExistingObject(entityA, descriptor); - instance.setStringAttribute("update"); - uow.attributeChanged(instance, OWLClassA.getStrAttField()); - uow.removeObject(instance); - final UnitOfWorkChangeSet changeSet = uow.getUowChangeSet(); - assertFalse(changeSet.getExistingObjectsChanges().isEmpty()); - uow.commit(); - assertTrue(changeSet.getExistingObjectsChanges().isEmpty()); - } - @Test void getManagedOriginalThrowsEntityExistsExceptionWhenIndividualIsManagedAsDifferentType() { when(transactionMock.isActive()).thenReturn(true); + defaultLoadStateDescriptor(entityA); uow.registerExistingObject(entityA, descriptor); assertThrows(OWLEntityExistsException.class, - () -> uow.getManagedOriginal(OWLClassB.class, entityA.getUri(), descriptor)); + () -> uow.getManagedOriginal(OWLClassB.class, entityA.getUri(), descriptor)); } @Test void getManagedOriginalReturnsNullWhenObjectIsManagedButAmongDeletedObjects() { when(transactionMock.isActive()).thenReturn(true); + defaultLoadStateDescriptor(entityA); final Object entity = uow.registerExistingObject(entityA, descriptor); assertNotNull(uow.getManagedOriginal(OWLClassA.class, entityA.getUri(), descriptor)); uow.removeObject(entity); @@ -1094,53 +979,85 @@ public int max() { assertDoesNotThrow(() -> uow.commit()); } - @Test - void attributeChangedThrowsInferredAttributeModifiedExceptionOnChangeToInferredAttributeValue() { - final OWLClassF original = new OWLClassF(Generators.createIndividualIdentifier()); - original.setSecondStringAttribute("Changed value"); - when(transactionMock.isActive()).thenReturn(true); - final OWLClassF instance = (OWLClassF) uow.registerExistingObject(original, descriptor); - // Ensure original and cloned value differ - original.setSecondStringAttribute("Original value"); - final Assertion assertion = - Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_f_stringAttribute), true); - doAnswer(inv -> { - final OWLClassF inst = inv.getArgument(0, OWLClassF.class); - return Collections.singleton(new AxiomImpl<>(NamedResource.create(inst.getUri()), assertion, - new Value<>(inst.getSecondStringAttribute()))); - }).when(storageMock).getAttributeAxioms(any(), any(), any()); - when(storageMock.isInferred(any(), any())).thenReturn(true); - - - assertThrows(InferredAttributeModifiedException.class, - () -> uow.attributeChanged(instance, OWLClassF.getStrAttField())); - verify(storageMock).isInferred(new AxiomImpl<>(NamedResource.create(original.getUri()), assertion, - new Value<>(original.getSecondStringAttribute())), - Collections.singleton(CONTEXT_URI)); - verify(storageMock, never()).merge(any(), eq(metamodelMocks.forOwlClassF().stringAttribute()), any()); - } - @Test void isInferredChecksForValueInferredStatusWithConnectionWrapper() { + defaultLoadStateDescriptor(entityD, entityA); final OWLClassD instance = (OWLClassD) uow.registerExistingObject(entityD, descriptor); uow.isInferred(instance, metamodelMocks.forOwlClassD().owlClassAAtt(), instance.getOwlClassA()); - verify(storageMock).isInferred(instance, metamodelMocks.forOwlClassD().owlClassAAtt(), instance.getOwlClassA(), descriptor); + verify(storageMock).isInferred(instance, metamodelMocks.forOwlClassD() + .owlClassAAtt(), instance.getOwlClassA(), descriptor); } @Test void isInferredThrowsIllegalArgumentExceptionWhenInstanceIsNotManaged() { - assertThrows(IllegalArgumentException.class, () -> uow.isInferred(entityD, metamodelMocks.forOwlClassD().owlClassAAtt(), entityD.getOwlClassA())); + assertThrows(IllegalArgumentException.class, () -> uow.isInferred(entityD, metamodelMocks.forOwlClassD() + .owlClassAAtt(), entityD.getOwlClassA())); verify(storageMock, never()).isInferred(any(), any(), any(), any()); } @Test - void unregisterObjectRemovesIndirectMultilingualStringOfManagedObjectBeingDetached() { + void commitPersistsAllNewlyRegisteredObjects() { when(transactionMock.isActive()).thenReturn(Boolean.TRUE); - final OWLClassU entityU = new OWLClassU(Generators.createIndividualIdentifier()); - entityU.setSingularStringAtt(MultilingualString.create("test", "en")); - final OWLClassU managed = (OWLClassU) uow.registerExistingObject(entityU, descriptor); - assertInstanceOf(IndirectMultilingualString.class, managed.getSingularStringAtt()); - uow.unregisterObject(managed); - assertThat(managed.getSingularStringAtt(), not(instanceOf(IndirectMultilingualString.class))); + uow.registerNewObject(entityA, descriptor); + uow.registerNewObject(entityB, descriptor); + verify(storageMock, never()).persist(any(), any(), any()); + uow.commit(); + + verify(storageMock).persist(entityA.getUri(), entityA, descriptor); + verify(storageMock).persist(entityB.getUri(), entityB, descriptor); + } + + @Test + void unregisterObjectReplacesPluralLazyLoadingProxiesWithEmptyCollection() { + when(transactionMock.isActive()).thenReturn(true); + defaultLoadStateDescriptor(entityL); + uow.getLoadStateRegistry().get(entityL) + .setLoaded(metamodelMocks.forOwlClassL().simpleListAtt(), LoadState.NOT_LOADED); + uow.getLoadStateRegistry().get(entityL) + .setLoaded(metamodelMocks.forOwlClassL().setAttribute(), LoadState.NOT_LOADED); + final OWLClassL clone = (OWLClassL) uow.registerExistingObject(entityL, descriptor); + // Simple list is lazy loaded + assertInstanceOf(LazyLoadingProxy.class, clone.getSimpleList()); + assertInstanceOf(LazyLoadingProxy.class, clone.getSet()); + uow.unregisterObject(clone); + assertNotNull(clone.getSimpleList()); + assertTrue(clone.getSimpleList().isEmpty()); + assertNotNull(clone.getSet()); + assertTrue(clone.getSet().isEmpty()); + } + + @Test + void unregisterObjectReplacesSingularLazyLoadingProxiesWithNull() { + when(transactionMock.isActive()).thenReturn(true); + defaultLoadStateDescriptor(entityL); + uow.getLoadStateRegistry().get(entityL) + .setLoaded(metamodelMocks.forOwlClassL().owlClassAAtt(), LoadState.NOT_LOADED); + when(metamodelMock.getLazyLoadingProxy(OWLClassA.class)).thenReturn((Class) new LazyLoadingEntityProxyGenerator().generate(OWLClassA.class)); + final OWLClassL clone = (OWLClassL) uow.registerExistingObject(entityL, descriptor); + assertInstanceOf(LazyLoadingProxy.class, clone.getSingleA()); + uow.unregisterObject(clone); + assertNull(clone.getSingleA()); + } + + @Test + void getInstanceForMergeTriggersLazyAttributeLoadingForAlreadyManagedObject() { + when(transactionMock.isActive()).thenReturn(true); + final List simpleList = Generators.generateInstances(2); + defaultLoadStateDescriptor(simpleList.toArray()); + final OWLClassC entityC = new OWLClassC(Generators.createIndividualIdentifier()); + defaultLoadStateDescriptor(entityC); + uow.getLoadStateRegistry().get(entityC) + .setLoaded(metamodelMocks.forOwlClassC().simpleListAtt(), LoadState.NOT_LOADED); + final OWLClassC clone = (OWLClassC) uow.registerExistingObject(entityC, descriptor); + assertInstanceOf(LazyLoadingProxy.class, clone.getSimpleList()); + doAnswer(inv -> { + final OWLClassC instance = inv.getArgument(0); + instance.setSimpleList(simpleList); + return simpleList; + }).when(storageMock).loadFieldValue(clone, metamodelMocks.forOwlClassC().simpleListAtt(), descriptor); + + final OWLClassC result = uow.getInstanceForMerge(entityC.getUri(), metamodelMocks.forOwlClassC() + .entityType(), descriptor); + assertThat(result.getSimpleList(), containsSameEntities(simpleList)); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWorkMergeTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWorkMergeTest.java new file mode 100644 index 000000000..de5e61eed --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWorkMergeTest.java @@ -0,0 +1,51 @@ +package cz.cvut.kbss.jopa.sessions; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.utils.Configuration; +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +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.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class ChangeTrackingUnitOfWorkMergeTest extends UnitOfWorkMergeTestRunner { + + @BeforeEach + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected AbstractUnitOfWork initUnitOfWork() { + return new ChangeTrackingUnitOfWork(serverSessionStub, new Configuration()); + } + + @Test + void testMergeDetachedExisting() { + final OWLClassA result = mergeDetached(); + assertNotNull(result); + assertEquals(entityA.getUri(), result.getUri()); + assertEquals(entityA.getStringAttribute(), result.getStringAttribute()); + final ArgumentCaptor ac = ArgumentCaptor.forClass(FieldSpecification.class); + verify(storageMock, atLeastOnce()).merge(any(Object.class), ac.capture(), eq(descriptor)); + final List mergedFields = ac.getAllValues(); + assertTrue(mergedFields.contains(metamodelMocks.forOwlClassA().stringAttribute())); + assertTrue(mergedFields.contains(metamodelMocks.forOwlClassA().typesSpec())); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWorkTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWorkTest.java new file mode 100644 index 000000000..1207a643d --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWorkTest.java @@ -0,0 +1,256 @@ +package cz.cvut.kbss.jopa.sessions; + +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectMultilingualString; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectSet; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassB; +import cz.cvut.kbss.jopa.environment.OWLClassF; +import cz.cvut.kbss.jopa.environment.OWLClassL; +import cz.cvut.kbss.jopa.environment.OWLClassR; +import cz.cvut.kbss.jopa.environment.OWLClassU; +import cz.cvut.kbss.jopa.environment.Vocabulary; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.exceptions.InferredAttributeModifiedException; +import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.model.EntityState; +import cz.cvut.kbss.jopa.model.LoadState; +import cz.cvut.kbss.jopa.model.MultilingualString; +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +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.LoadingParameters; +import cz.cvut.kbss.jopa.utils.Configuration; +import cz.cvut.kbss.ontodriver.model.Assertion; +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import java.lang.reflect.Field; +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +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; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class ChangeTrackingUnitOfWorkTest extends AbstractUnitOfWorkTestRunner { + + @BeforeEach + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected AbstractUnitOfWork initUnitOfWork() { + return new ChangeTrackingUnitOfWork(serverSessionStub, new Configuration()); + } + + @Test + void refreshCancelsObjectChangesInUnitOfWorkChangeSet() throws Exception { + when(transactionMock.isActive()).thenReturn(true); + defaultLoadStateDescriptor(entityA); + final OWLClassA a = (OWLClassA) uow.registerExistingObject(entityA, descriptor); + a.setStringAttribute("updatedString"); + uow.attributeChanged(a, OWLClassA.getStrAttField()); + final OWLClassA original = new OWLClassA(entityA.getUri()); + original.setStringAttribute(entityA.getStringAttribute()); + original.setTypes(new HashSet<>(entityA.getTypes())); + when(storageMock.find(any())).thenReturn(original); + defaultLoadStateDescriptor(original); + assertNotNull(uow.uowChangeSet.getExistingObjectChanges(entityA)); + uow.refreshObject(a); + assertNull(uow.uowChangeSet.getExistingObjectChanges(entityA)); + assertNull(uow.uowChangeSet.getExistingObjectChanges(original)); + } + + @Test + void commitReplacesIndirectCollectionsWithRegularOnesInDetachedInstances() { + uow.registerNewObject(entityA, descriptor); + assertInstanceOf(ChangeTrackingIndirectSet.class, entityA.getTypes()); + uow.commit(); + assertThat(entityA.getTypes(), not(instanceOf(ChangeTrackingIndirectSet.class))); + } + + @Test + void detachReplacesInheritedIndirectCollectionWithRegularOne() { + final OWLClassR entityR = new OWLClassR(Generators.createIndividualIdentifier()); + entityR.setName("test"); + final Set types = Generators.generateTypes(3); + entityR.setTypes(types); + uow.registerNewObject(entityR, descriptor); + assertInstanceOf(ChangeTrackingIndirectSet.class, entityR.getTypes()); + assertEquals(types, entityR.getTypes()); + uow.commit(); + assertThat(entityR.getTypes(), not(instanceOf(ChangeTrackingIndirectSet.class))); + assertEquals(types, entityR.getTypes()); + } + + @Test + void attributeChangedOutsideTransactionThrowsIllegalStateException() throws Exception { + uow.clear(); + final Field strField = OWLClassA.getStrAttField(); + assertThrows(IllegalStateException.class, () -> uow.attributeChanged(entityA, strField)); + verify(storageMock, never()).merge(any(OWLClassA.class), eq(metamodelMocks.forOwlClassA().stringAttribute()), + eq(descriptor)); + } + + @Test + void attributeChangedMergesChangeToStorage() throws Exception { + when(transactionMock.isActive()).thenReturn(Boolean.TRUE); + defaultLoadStateDescriptor(entityA); + final OWLClassA clone = (OWLClassA) uow.registerExistingObject(entityA, descriptor); + final Field strField = OWLClassA.getStrAttField(); + + uow.attributeChanged(clone, strField); + verify(storageMock).merge(clone, metamodelMocks.forOwlClassA().stringAttribute(), descriptor); + } + + @Test + void attributeChangedOnUnmanagedObjectThrowsOwlPersistenceException() throws Exception { + when(transactionMock.isActive()).thenReturn(Boolean.TRUE); + final Field strField = OWLClassA.getStrAttField(); + assertThrows(OWLPersistenceException.class, () -> uow.attributeChanged(entityA, strField)); + verify(storageMock, never()).merge(any(OWLClassA.class), eq(metamodelMocks.forOwlClassA().stringAttribute()), + eq(descriptor)); + } + + @Test + void attributeChangedThrowsInferredAttributeModifiedExceptionOnChangeToInferredAttributeValue() { + final OWLClassF original = new OWLClassF(Generators.createIndividualIdentifier()); + original.setSecondStringAttribute("Changed value"); + when(transactionMock.isActive()).thenReturn(true); + defaultLoadStateDescriptor(original); + final OWLClassF instance = (OWLClassF) uow.registerExistingObject(original, descriptor); + // Ensure original and cloned value differ + original.setSecondStringAttribute("Original value"); + final Assertion assertion = + Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_f_stringAttribute), true); + doAnswer(inv -> { + final OWLClassF inst = inv.getArgument(0, OWLClassF.class); + return Collections.singleton(new AxiomImpl<>(NamedResource.create(inst.getUri()), assertion, + new Value<>(inst.getSecondStringAttribute()))); + }).when(storageMock).getAttributeAxioms(any(), any(), any()); + when(storageMock.isInferred(any(), any())).thenReturn(true); + + + assertThrows(InferredAttributeModifiedException.class, + () -> uow.attributeChanged(instance, OWLClassF.getStrAttField())); + verify(storageMock).isInferred(new AxiomImpl<>(NamedResource.create(original.getUri()), assertion, + new Value<>(original.getSecondStringAttribute())), + Collections.singleton(CONTEXT_URI)); + verify(storageMock, never()).merge(any(), eq(metamodelMocks.forOwlClassF().stringAttribute()), any()); + } + + @Test + void attributeChangedSetsAttributeLoadStatusToLoaded() throws Exception { + when(transactionMock.isActive()).thenReturn(Boolean.TRUE); + final LoadStateDescriptor loadStateDescriptor = LoadStateDescriptorFactory.createNotLoaded(entityL, metamodelMocks.forOwlClassL() + .entityType()); + when(metamodelMock.getLazyLoadingProxy(OWLClassA.class)).thenReturn((Class) new LazyLoadingEntityProxyGenerator().generate(OWLClassA.class)); + uow.getLoadStateRegistry().put(entityL, loadStateDescriptor); + final OWLClassL instance = (OWLClassL) uow.registerExistingObject(entityL, descriptor); + assertEquals(LoadState.NOT_LOADED, uow.isLoaded(instance, OWLClassL.getSetField().getName())); + instance.setSet(Collections.singleton(entityA)); + uow.attributeChanged(instance, OWLClassL.getSetField()); + + assertEquals(LoadState.LOADED, uow.isLoaded(instance, OWLClassL.getSetField().getName())); + } + + @Test + void unregisterObjectRemovesIndirectMultilingualStringOfManagedObjectBeingDetached() { + when(transactionMock.isActive()).thenReturn(Boolean.TRUE); + final OWLClassU entityU = new OWLClassU(Generators.createIndividualIdentifier()); + entityU.setSingularStringAtt(MultilingualString.create("test", "en")); + defaultLoadStateDescriptor(entityU); + final OWLClassU managed = (OWLClassU) uow.registerExistingObject(entityU, descriptor); + assertInstanceOf(ChangeTrackingIndirectMultilingualString.class, managed.getSingularStringAtt()); + uow.unregisterObject(managed); + assertThat(managed.getSingularStringAtt(), not(instanceOf(ChangeTrackingIndirectMultilingualString.class))); + } + + @Test + void removeObjectPutsExistingObjectIntoDeletedCacheAndRemovesItFromRepository() { + defaultLoadStateDescriptor(entityB); + final OWLClassB toRemove = (OWLClassB) uow.registerExistingObject(entityB, descriptor); + uow.removeObject(toRemove); + assertFalse(uow.contains(toRemove)); + assertEquals(EntityState.REMOVED, uow.getState(toRemove)); + verify(storageMock).remove(entityB.getUri(), entityB.getClass(), descriptor); + } + + @Test + void changesToRemovedObjectAreIgnoredOnCommit() throws Exception { + when(transactionMock.isActive()).thenReturn(Boolean.TRUE); + defaultLoadStateDescriptor(entityA); + final OWLClassA instance = (OWLClassA) uow.registerExistingObject(entityA, descriptor); + instance.setStringAttribute("update"); + uow.attributeChanged(instance, OWLClassA.getStrAttField()); + uow.removeObject(instance); + assertFalse(uow.uowChangeSet.getExistingObjectsChanges().isEmpty()); + uow.commit(); + assertTrue(uow.uowChangeSet.getExistingObjectsChanges().isEmpty()); + } + + @Test + void registerReplacesAlsoInheritedCollectionInstancesWithIndirectVersions() { + final OWLClassR entityR = new OWLClassR(Generators.createIndividualIdentifier()); + entityR.setTypes(Generators.generateTypes(5)); + when(storageMock.find(new LoadingParameters<>(OWLClassR.class, entityR.getUri(), descriptor))) + .thenReturn(entityR); + defaultLoadStateDescriptor(entityR); + final OWLClassR clone = uow.readObject(OWLClassR.class, entityR.getUri(), descriptor); + assertInstanceOf(ChangeTrackingIndirectSet.class, clone.getTypes()); + } + + @Test + void releaseRemovesIndirectCollectionsFromManagedEntities() { + when(storageMock.find(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor, false))) + .thenReturn(entityA); + defaultLoadStateDescriptor(entityA); + final OWLClassA result = uow.readObject(OWLClassA.class, entityA.getUri(), descriptor); + assertNotNull(result); + assertInstanceOf(ChangeTrackingIndirectSet.class, result.getTypes()); + uow.release(); + assertThat(result.getTypes(), not(instanceOf(ChangeTrackingIndirectSet.class))); + } + + @Test + void loadEntityFieldDoesNotInvokeLoadFromRepositoryForNullAttributeWhenItsStateIsLoaded() throws Exception { + when(transactionMock.isActive()).thenReturn(Boolean.TRUE); + final LoadStateDescriptor loadStateDescriptor = LoadStateDescriptorFactory.createNotLoaded(entityL, metamodelMocks.forOwlClassL() + .entityType()); + when(metamodelMock.getLazyLoadingProxy(OWLClassA.class)).thenReturn((Class) new LazyLoadingEntityProxyGenerator().generate(OWLClassA.class)); + uow.getLoadStateRegistry().put(entityL, loadStateDescriptor); + final OWLClassL instance = (OWLClassL) uow.registerExistingObject(entityL, descriptor); + assertEquals(LoadState.NOT_LOADED, uow.isLoaded(instance, OWLClassL.getSetField().getName())); + uow.attributeChanged(instance, OWLClassL.getSetField()); + assertEquals(LoadState.LOADED, uow.isLoaded(instance, OWLClassL.getSetField().getName())); + uow.loadEntityField(instance, metamodelMocks.forOwlClassL().setAttribute()); + verify(storageMock, never()).loadFieldValue(eq(instance), eq(metamodelMocks.forOwlClassL().setAttribute()), + any(Descriptor.class)); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/CloneBuilderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/CloneBuilderTest.java index caadd3591..1eae8e4f1 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/CloneBuilderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/CloneBuilderTest.java @@ -17,39 +17,87 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectCollection; -import cz.cvut.kbss.jopa.environment.*; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassB; +import cz.cvut.kbss.jopa.environment.OWLClassC; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.OWLClassG; +import cz.cvut.kbss.jopa.environment.OWLClassH; +import cz.cvut.kbss.jopa.environment.OWLClassM; +import cz.cvut.kbss.jopa.environment.OWLClassO; +import cz.cvut.kbss.jopa.environment.OWLClassQ; +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.environment.utils.TestEnvironmentUtils; +import cz.cvut.kbss.jopa.model.LoadState; import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.Id; 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.*; -import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl; +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.CollectionType; +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.model.metamodel.IRIIdentifierImpl; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; +import cz.cvut.kbss.jopa.model.metamodel.Identifier; +import cz.cvut.kbss.jopa.model.metamodel.PluralAttribute; +import cz.cvut.kbss.jopa.model.metamodel.SingularAttribute; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectCollection; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory; +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.CloneConfiguration; +import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; +import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; 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.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import java.lang.reflect.Field; import java.net.URI; import java.time.Instant; import java.time.LocalDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) public class CloneBuilderTest { - private CloneBuilderImpl builder; + private CloneBuilder builder; private OWLClassA entityA; private OWLClassB entityB; @@ -61,7 +109,9 @@ public class CloneBuilderTest { private static EntityDescriptor defaultDescriptor; @Mock - private UnitOfWorkImpl uow; + private AbstractUnitOfWork uow; + + private final LoadStateDescriptorRegistry loadStateRegistry = new LoadStateDescriptorRegistry(Object::toString); @Mock private MetamodelImpl metamodel; @@ -89,27 +139,27 @@ private static void initManagedTypes() { @BeforeEach public void setUp() throws Exception { - MockitoAnnotations.openMocks(this); when(uow.isEntityType(any())).thenAnswer(invocation -> { Class cls = (Class) invocation.getArguments()[0]; return managedTypes.contains(cls); }); when(uow.getMetamodel()).thenReturn(metamodel); - when(uow.registerExistingObject(any(), any(), any())).thenAnswer( + when(uow.registerExistingObject(any(), any(CloneRegistrationDescriptor.class))).thenAnswer( invocation -> { Object obj = invocation.getArguments()[0]; - Descriptor desc = (Descriptor) invocation.getArguments()[1]; - return builder.buildClone(obj, new CloneConfiguration(desc)); + CloneRegistrationDescriptor desc = (CloneRegistrationDescriptor) invocation.getArguments()[1]; + return builder.buildClone(obj, new CloneConfiguration(desc.getDescriptor(), true)); }); this.metamodelMocks = new MetamodelMocks(); metamodelMocks.setMocks(metamodel); - this.builder = new CloneBuilderImpl(uow); + this.builder = new CloneBuilder(uow); initValues(); final IndirectWrapperHelper cf = new IndirectWrapperHelper(uow); when(uow.createIndirectCollection(any(), any(), any())).thenAnswer(call -> { final Object[] args = call.getArguments(); return cf.createIndirectWrapper(args[0], args[1], (Field) args[2]); }); + when(uow.getLoadStateRegistry()).thenReturn(loadStateRegistry); } private void initValues() { @@ -142,16 +192,22 @@ private void initValues() { @Test public void testBuildClone() { - OWLClassA res = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); + OWLClassA res = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor, false)); assertEquals(res.getStringAttribute(), entityA.getStringAttribute()); assertEquals(res.getUri(), entityA.getUri()); assertEquals(entityA.getTypes(), res.getTypes()); } + private void defaultLoadStateDescriptor(T entity, EntityType et) { + final LoadStateDescriptor desc = LoadStateDescriptorFactory.createAllLoaded(entity, et); + loadStateRegistry.put(entity, desc); + } + @Test public void testBuildCloneNullOriginal() { assertThrows(NullPointerException.class, - () -> builder.buildClone(null, new CloneConfiguration(defaultDescriptor))); + () -> builder.buildClone(null, new CloneConfiguration(defaultDescriptor, false))); } @Test @@ -162,12 +218,13 @@ public void testBuildCloneNullContextUri() { @Test public void testBuildCloneNullCloneOwner() { assertThrows(NullPointerException.class, - () -> builder.buildClone(null, OWLClassB.getPropertiesField(), entityB, defaultDescriptor)); + () -> builder.buildClone(null, OWLClassB.getPropertiesField(), entityB, defaultDescriptor)); } @Test public void testCloneCollection() { - final OWLClassA clone = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); + final OWLClassA clone = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor, false)); assertEquals(entityA.getTypes().size(), clone.getTypes().size()); for (String t : entityA.getTypes()) { assertTrue(clone.getTypes().contains(t)); @@ -176,20 +233,22 @@ public void testCloneCollection() { @Test public void testBuildCloneTwice() { - OWLClassA res = (OWLClassA) this.builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); + OWLClassA res = (OWLClassA) this.builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor, false)); assertEquals(res.getStringAttribute(), entityA.getStringAttribute()); assertEquals(res.getUri(), entityA.getUri()); assertEquals(entityA.getTypes(), res.getTypes()); assertNotSame(entityA, res); - final OWLClassA resTwo = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor)); + final OWLClassA resTwo = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor, false)); assertSame(res, resTwo); } @Test public void testBuildCloneOriginalInUoW() { + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); when(uow.containsOriginal(entityA)).thenReturn(Boolean.TRUE); when(uow.getCloneForOriginal(entityA)).thenReturn(entityA); - final OWLClassA res = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor)); + final OWLClassA res = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(res); assertSame(entityA, res); } @@ -201,7 +260,7 @@ public void testCloneListCollection() { testList.add("Two"); testList.add("Three"); @SuppressWarnings("unchecked") - List clone = (List) builder.buildClone(testList, new CloneConfiguration(defaultDescriptor)); + List clone = (List) builder.buildClone(testList, new CloneConfiguration(defaultDescriptor, false)); assertEquals(testList.size(), clone.size()); Iterator it1 = testList.iterator(); Iterator it2 = clone.iterator(); @@ -212,22 +271,21 @@ public void testCloneListCollection() { @Test public void testCloneSingletonCollection() { - final OWLClassA obj = new OWLClassA(); - final URI pk = URI.create("http://singletonTest"); - obj.setUri(pk); - obj.setStringAttribute("TEST"); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); String type = "A_type"; - obj.setTypes(Collections.singleton(type)); - final OWLClassA result = (OWLClassA) builder.buildClone(obj, new CloneConfiguration(defaultDescriptor)); + entityA.setTypes(Collections.singleton(type)); + final OWLClassA result = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor, false)); assertEquals(1, result.getTypes().size()); assertEquals(type, result.getTypes().iterator().next()); } @Test public void testCloneEmptyList() { + defaultLoadStateDescriptor(entityC, metamodelMocks.forOwlClassC().entityType()); entityC.setSimpleList(Collections.emptyList()); entityC.setReferencedList(Generators.generateInstances(10)); - final OWLClassC res = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor)); + entityC.getReferencedList().forEach(a -> defaultLoadStateDescriptor(a, metamodelMocks.forOwlClassA().entityType())); + final OWLClassC res = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(res); assertTrue(res.getSimpleList().isEmpty()); for (int i = 0; i < res.getReferencedList().size(); i++) { @@ -237,30 +295,28 @@ public void testCloneEmptyList() { @Test public void testCloneEmptySet() { - final OWLClassA obj = new OWLClassA(); - final URI pk = URI.create("http://singletonTest"); - obj.setUri(pk); - obj.setStringAttribute("TEST"); - obj.setTypes(Collections.emptySet()); - final OWLClassA res = (OWLClassA) builder.buildClone(obj, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); + entityA.setTypes(Collections.emptySet()); + final OWLClassA res = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(res); assertTrue(res.getTypes().isEmpty()); } @Test public void testCloneProperties() { + defaultLoadStateDescriptor(entityB, metamodelMocks.forOwlClassB().entityType()); entityB.setProperties(Generators.generateStringProperties()); - OWLClassB res = (OWLClassB) builder.buildClone(entityB, new CloneConfiguration(defaultDescriptor)); + OWLClassB res = (OWLClassB) builder.buildClone(entityB, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(res); assertEquals(entityB.getUri(), res.getUri()); assertEquals(entityB.getStringAttribute(), res.getStringAttribute()); assertEquals(entityB.getProperties().size(), res.getProperties().size()); - assertTrue(res.getProperties() instanceof IndirectCollection); + assertInstanceOf(ChangeTrackingIndirectCollection.class, res.getProperties()); for (Entry> e : entityB.getProperties().entrySet()) { final String k = e.getKey(); assertTrue(res.getProperties().containsKey(k)); final Set rv = res.getProperties().get(k); - assertTrue(rv instanceof IndirectCollection); + assertInstanceOf(ChangeTrackingIndirectCollection.class, rv); assertEquals(e.getValue().size(), rv.size()); for (String s : e.getValue()) { assertTrue(rv.contains(s)); @@ -270,24 +326,28 @@ public void testCloneProperties() { @Test public void testCloneEmptyMap() { + defaultLoadStateDescriptor(entityB, metamodelMocks.forOwlClassB().entityType()); entityB.setProperties(Collections.emptyMap()); - OWLClassB res = (OWLClassB) builder.buildClone(entityB, new CloneConfiguration(defaultDescriptor)); + OWLClassB res = (OWLClassB) builder.buildClone(entityB, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(res); assertTrue(res.getProperties().isEmpty()); } @Test public void testCloneObjectProperty() { + defaultLoadStateDescriptor(entityD, metamodelMocks.forOwlClassD().entityType()); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); final OWLClassD another = new OWLClassD(); another.setUri(URI.create("http://krizik.felk.cvut.cz/ontologies/jopa/tests/entityDD")); another.setOwlClassA(entityA); - final OWLClassD clOne = (OWLClassD) builder.buildClone(entityD, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(another, metamodelMocks.forOwlClassD().entityType()); + final OWLClassD clOne = (OWLClassD) builder.buildClone(entityD, new CloneConfiguration(defaultDescriptor, false)); assertNotSame(entityD, clOne); assertNotSame(entityA, clOne.getOwlClassA()); - final OWLClassD clTwo = (OWLClassD) builder.buildClone(another, new CloneConfiguration(defaultDescriptor)); + final OWLClassD clTwo = (OWLClassD) builder.buildClone(another, new CloneConfiguration(defaultDescriptor, false)); assertSame(clOne.getOwlClassA(), clTwo.getOwlClassA()); assertEquals(entityA.getStringAttribute(), clOne.getOwlClassA().getStringAttribute()); - assertTrue(clOne.getOwlClassA().getTypes() instanceof IndirectCollection); + assertInstanceOf(ChangeTrackingIndirectCollection.class, clOne.getOwlClassA().getTypes()); final Set tps = clOne.getOwlClassA().getTypes(); assertEquals(entityA.getTypes().size(), tps.size()); for (String t : entityA.getTypes()) { @@ -297,64 +357,57 @@ public void testCloneObjectProperty() { @Test public void testCloneWithNullCollection() { + defaultLoadStateDescriptor(entityB, metamodelMocks.forOwlClassB().entityType()); assertNull(entityB.getProperties()); - final OWLClassB res = (OWLClassB) builder.buildClone(entityB, new CloneConfiguration(defaultDescriptor)); + final OWLClassB res = (OWLClassB) builder.buildClone(entityB, new CloneConfiguration(defaultDescriptor, false)); assertNotSame(entityB, res); assertNull(res.getProperties()); } - @Test - public void testCloneSingletonSet() { - final Set singleton = Collections - .singleton("http://krizik.felk.cvut.cz/ontologies/jopa/tests/entityY"); - entityA.setTypes(singleton); - final OWLClassA res = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor)); - assertNotSame(entityA, res); - assertTrue(res.getTypes() instanceof IndirectCollection); - assertEquals(1, res.getTypes().size()); - assertTrue(res.getTypes().contains(singleton.iterator().next())); - } - @Test public void testCloneSingletonMap() { + defaultLoadStateDescriptor(entityB, metamodelMocks.forOwlClassB().entityType()); final String key = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#attr"; final String value = "stringValue"; final Map> m = Collections.singletonMap(key, - Collections.singleton(value)); + Collections.singleton(value)); entityB.setProperties(m); - final OWLClassB res = (OWLClassB) builder.buildClone(entityB, new CloneConfiguration(defaultDescriptor)); + final OWLClassB res = (OWLClassB) builder.buildClone(entityB, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(res); assertNotSame(entityB, res); assertEquals(1, res.getProperties().size()); - assertTrue(res.getProperties() instanceof IndirectCollection); + assertInstanceOf(ChangeTrackingIndirectCollection.class, res.getProperties()); final Set s = res.getProperties().get(key); - assertTrue(s instanceof IndirectCollection); + assertInstanceOf(ChangeTrackingIndirectCollection.class, s); assertEquals(1, s.size()); assertEquals(value, s.iterator().next()); } @Test public void testCloneSingletonListWithReference() { + defaultLoadStateDescriptor(entityC, metamodelMocks.forOwlClassC().entityType()); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); entityC.setReferencedList(Collections.singletonList(entityA)); - final OWLClassC res = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor)); + final OWLClassC res = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor, false)); assertNotSame(res, entityC); assertEquals(1, res.getReferencedList().size()); - assertTrue(res.getReferencedList() instanceof IndirectCollection); + assertInstanceOf(ChangeTrackingIndirectCollection.class, res.getReferencedList()); final OWLClassA a = res.getReferencedList().get(0); assertNotSame(entityA, a); assertEquals(entityA.getUri(), a.getUri()); - assertTrue(a.getTypes() instanceof IndirectCollection); + assertInstanceOf(ChangeTrackingIndirectCollection.class, a.getTypes()); } @Test public void testCloneReferencedList() { - // Let's see how long this takes - entityC.setReferencedList(Generators.generateInstances(100)); - final OWLClassC res = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityC, metamodelMocks.forOwlClassC().entityType()); + entityC.setReferencedList(Generators.generateInstances(10)); + entityC.getReferencedList().forEach(a -> defaultLoadStateDescriptor(a, metamodelMocks.forOwlClassA().entityType())); + final OWLClassC res = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor, false)); assertNotSame(entityC, res); int size = entityC.getReferencedList().size(); assertEquals(size, res.getReferencedList().size()); - assertTrue(res.getReferencedList() instanceof IndirectCollection); + assertInstanceOf(ChangeTrackingIndirectCollection.class, res.getReferencedList()); for (int i = 0; i < size; i++) { final OWLClassA or = entityC.getReferencedList().get(i); final OWLClassA cl = res.getReferencedList().get(i); @@ -362,22 +415,20 @@ public void testCloneReferencedList() { assertEquals(or.getUri(), cl.getUri()); assertEquals(or.getStringAttribute(), cl.getStringAttribute()); assertEquals(or.getTypes().size(), cl.getTypes().size()); - assertTrue(cl.getTypes() instanceof IndirectCollection); + assertInstanceOf(ChangeTrackingIndirectCollection.class, cl.getTypes()); } } @Test public void testCloneReferencedListWithNulls() { - final List nulls = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - nulls.add(null); - } + defaultLoadStateDescriptor(entityC, metamodelMocks.forOwlClassC().entityType()); + final List nulls = new ArrayList<>(Arrays.asList(null, null, null, null)); entityC.setReferencedList(nulls); - final OWLClassC res = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor)); + final OWLClassC res = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor, false)); assertNotSame(entityC, res); int size = entityC.getReferencedList().size(); assertEquals(size, res.getReferencedList().size()); - assertTrue(res.getReferencedList() instanceof IndirectCollection); + assertInstanceOf(ChangeTrackingIndirectCollection.class, res.getReferencedList()); for (OWLClassA a : res.getReferencedList()) { assertNull(a); } @@ -385,16 +436,15 @@ public void testCloneReferencedListWithNulls() { @Test public void testMergeChangesOnString() { - final OWLClassA a = new OWLClassA(); - a.setUri(entityA.getUri()); + final OWLClassA a = new OWLClassA(entityA.getUri()); a.setStringAttribute("oldString"); - final OWLClassA cloneA = (OWLClassA) builder.buildClone(a, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(a, metamodelMocks.forOwlClassA().entityType()); + final OWLClassA cloneA = (OWLClassA) builder.buildClone(a, new CloneConfiguration(defaultDescriptor, false)); final String newStrAtt = "newString"; cloneA.setStringAttribute(newStrAtt); - final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(a, cloneA, - defaultDescriptor); - chSet.addChangeRecord(new ChangeRecordImpl(metamodelMocks.forOwlClassA().stringAttribute(), - newStrAtt)); + final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(a, cloneA, defaultDescriptor); + chSet.addChangeRecord(new ChangeRecord(metamodelMocks.forOwlClassA().stringAttribute(), + newStrAtt)); builder.mergeChanges(chSet); assertEquals(newStrAtt, a.getStringAttribute()); @@ -402,13 +452,14 @@ public void testMergeChangesOnString() { @Test public void testMergeChangesPropertiesFromNull() { - final OWLClassB b = (OWLClassB) builder.buildClone(entityB, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityB, metamodelMocks.forOwlClassB().entityType()); + final OWLClassB b = (OWLClassB) builder.buildClone(entityB, new CloneConfiguration(defaultDescriptor, false)); assertNull(b.getProperties()); b.setProperties(Generators.generateStringProperties()); final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(entityB, b, - defaultDescriptor); - chSet.addChangeRecord(new ChangeRecordImpl(metamodelMocks.forOwlClassB().propertiesSpec(), - b.getProperties())); + defaultDescriptor); + chSet.addChangeRecord(new ChangeRecord(metamodelMocks.forOwlClassB().propertiesSpec(), + b.getProperties())); builder.mergeChanges(chSet); assertNotNull(entityB.getProperties()); @@ -417,14 +468,14 @@ public void testMergeChangesPropertiesFromNull() { @Test public void testMergeChangesRefListFromNull() { - final OWLClassC c = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityC, metamodelMocks.forOwlClassC().entityType()); + final OWLClassC c = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor, false)); assertNotSame(entityC, c); assertNull(entityC.getReferencedList()); c.setReferencedList(Generators.generateInstances(5)); - final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(entityC, c, - defaultDescriptor); - chSet.addChangeRecord(new ChangeRecordImpl(metamodelMocks.forOwlClassC().referencedListAtt(), - c.getReferencedList())); + final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(entityC, c, defaultDescriptor); + chSet.addChangeRecord(new ChangeRecord(metamodelMocks.forOwlClassC().referencedListAtt(), + c.getReferencedList())); builder.mergeChanges(chSet); assertNotNull(entityC.getReferencedList()); @@ -436,14 +487,15 @@ public void testMergeChangesRefListFromNull() { @Test public void mergeChangesHandlesSingletonCollection() { + defaultLoadStateDescriptor(entityC, metamodelMocks.forOwlClassC().entityType()); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); entityC.setReferencedList(Collections.singletonList(entityA)); - final OWLClassC c = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor)); + final OWLClassC c = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor, false)); final OWLClassA newA = new OWLClassA(); c.setReferencedList(Collections.singletonList(newA)); - final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(entityC, c, - defaultDescriptor); + final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(entityC, c, defaultDescriptor); chSet.addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassC().referencedListAtt(), c.getReferencedList())); + new ChangeRecord(metamodelMocks.forOwlClassC().referencedListAtt(), c.getReferencedList())); builder.mergeChanges(chSet); assertNotNull(entityC.getReferencedList()); @@ -453,7 +505,8 @@ public void mergeChangesHandlesSingletonCollection() { @Test public void testBuildCloneWithMultipleWrapperTypesAndStringKey() { - final OWLClassM m = (OWLClassM) builder.buildClone(entityM, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityM, metamodelMocks.forOwlClassM().entityType()); + final OWLClassM m = (OWLClassM) builder.buildClone(entityM, new CloneConfiguration(defaultDescriptor, false)); assertNotSame(entityM, m); assertEquals(entityM.getKey(), m.getKey()); assertEquals(entityM.getBooleanAttribute(), m.getBooleanAttribute()); @@ -465,25 +518,25 @@ public void testBuildCloneWithMultipleWrapperTypesAndStringKey() { @Test public void testMergeChangesWithMultipleWrapperTypesAndStringKey() { - final OWLClassM m = (OWLClassM) builder.buildClone(entityM, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityM, metamodelMocks.forOwlClassM().entityType()); + final OWLClassM m = (OWLClassM) builder.buildClone(entityM, new CloneConfiguration(defaultDescriptor, false)); assertNotSame(entityM, m); final ObjectChangeSet changeSet = ChangeSetFactory.createObjectChangeSet(entityM, m, defaultDescriptor); m.setBooleanAttribute(!m.getBooleanAttribute()); changeSet.addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassM().booleanAttribute(), m.getBooleanAttribute())); + new ChangeRecord(metamodelMocks.forOwlClassM().booleanAttribute(), m.getBooleanAttribute())); m.setIntAttribute(11111); - changeSet - .addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassM().integerAttribute(), m.getIntAttribute())); + changeSet.addChangeRecord( + new ChangeRecord(metamodelMocks.forOwlClassM().integerAttribute(), m.getIntAttribute())); m.setLongAttribute(999L); changeSet.addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassM().longAttribute(), m.getLongAttribute())); + new ChangeRecord(metamodelMocks.forOwlClassM().longAttribute(), m.getLongAttribute())); m.setDoubleAttribute(1.1); changeSet.addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassM().doubleAttribute(), m.getDoubleAttribute())); + new ChangeRecord(metamodelMocks.forOwlClassM().doubleAttribute(), m.getDoubleAttribute())); m.setDateAttribute(new Date(System.currentTimeMillis() + 10000L)); changeSet.addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassM().dateAttribute(), m.getDateAttribute())); + new ChangeRecord(metamodelMocks.forOwlClassM().dateAttribute(), m.getDateAttribute())); builder.mergeChanges(changeSet); assertEquals(m.getBooleanAttribute(), entityM.getBooleanAttribute()); @@ -500,8 +553,9 @@ public void cloningSkipsTransientFields() { entityO.setStringAttribute("String"); entityO.setTransientField("Transient"); entityO.setTransientFieldWithAnnotation("TransientAnnotated"); + defaultLoadStateDescriptor(entityO, metamodelMocks.forOwlClassO().entityType()); - final OWLClassO clone = (OWLClassO) builder.buildClone(entityO, new CloneConfiguration(defaultDescriptor)); + final OWLClassO clone = (OWLClassO) builder.buildClone(entityO, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(clone); assertEquals(entityO.getUri(), clone.getUri()); assertEquals(entityO.getStringAttribute(), clone.getStringAttribute()); @@ -511,11 +565,13 @@ public void cloningSkipsTransientFields() { @Test public void reusesAlreadyClonedInstancesWhenCloningCollections() { - final OWLClassA cloneA = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); + final OWLClassA cloneA = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor, false)); entityC.setReferencedList(new ArrayList<>()); entityC.getReferencedList().add(entityA); + defaultLoadStateDescriptor(entityC, metamodelMocks.forOwlClassC().entityType()); - final OWLClassC cloneC = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor)); + final OWLClassC cloneC = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(cloneC.getReferencedList()); assertEquals(1, cloneC.getReferencedList().size()); assertSame(cloneA, cloneC.getReferencedList().get(0)); @@ -524,16 +580,18 @@ public void reusesAlreadyClonedInstancesWhenCloningCollections() { @Test public void cloneBuildingHandlesCyclesInObjectGraphByRegisteringAlreadyVisitedObjects() { final OWLClassG entityG = initGWithBackwardReference(); + defaultLoadStateDescriptor(entityG, metamodelMocks.forOwlClassG().entityType()); + defaultLoadStateDescriptor(entityG.getOwlClassH(), metamodelMocks.forOwlClassH().entityType()); - final OWLClassG cloneG = (OWLClassG) builder.buildClone(entityG, new CloneConfiguration(defaultDescriptor)); + final OWLClassG cloneG = (OWLClassG) builder.buildClone(entityG, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(cloneG); assertNotNull(cloneG.getOwlClassH()); assertSame(cloneG, cloneG.getOwlClassH().getOwlClassG()); } private OWLClassG initGWithBackwardReference() { - final OWLClassG g = new OWLClassG(URI.create("http://krizik.felk.cvut.cz/ontologies#entityG")); - final OWLClassH h = new OWLClassH(URI.create("http://krizik.felk.cvut.cz/ontologies#entityH")); + final OWLClassG g = new OWLClassG(URI.create(Vocabulary.INDIVIDUAL_BASE + "entityG")); + final OWLClassH h = new OWLClassH(URI.create(Vocabulary.INDIVIDUAL_BASE + "entityH")); g.setOwlClassH(h); h.setOwlClassG(g); return g; @@ -544,8 +602,11 @@ public void testBuildCloneOfInstanceWithArrayAsListFieldValue() { final OWLClassC instance = new OWLClassC(URI.create("http://test")); final OWLClassA another = new OWLClassA(URI.create("http://anotherA")); instance.setSimpleList(Arrays.asList(entityA, another)); + defaultLoadStateDescriptor(instance, metamodelMocks.forOwlClassC().entityType()); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); + defaultLoadStateDescriptor(another, metamodelMocks.forOwlClassA().entityType()); - final OWLClassC result = (OWLClassC) builder.buildClone(instance, new CloneConfiguration(defaultDescriptor)); + final OWLClassC result = (OWLClassC) builder.buildClone(instance, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(result); for (int i = 0; i < instance.getSimpleList().size(); i++) { assertEquals(instance.getSimpleList().get(i).getUri(), result.getSimpleList().get(i).getUri()); @@ -554,6 +615,7 @@ public void testBuildCloneOfInstanceWithArrayAsListFieldValue() { @Test public void mergeOfFieldOfManagedTypeUsesOriginalValueForMerge() { + defaultLoadStateDescriptor(entityD, metamodelMocks.forOwlClassD().entityType()); entityD.setOwlClassA(entityA); final OWLClassD dClone = new OWLClassD(); dClone.setUri(entityD.getUri()); @@ -566,8 +628,8 @@ public void mergeOfFieldOfManagedTypeUsesOriginalValueForMerge() { newValueClone.setStringAttribute(newValue.getStringAttribute()); dClone.setOwlClassA(newValueClone); - final ObjectChangeSet chSet = TestEnvironmentUtils.createObjectChangeSet(entityD, dClone, null); - chSet.addChangeRecord(new ChangeRecordImpl(metamodelMocks.forOwlClassD().owlClassAAtt(), newValueClone)); + final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(entityD, dClone, new EntityDescriptor()); + chSet.addChangeRecord(new ChangeRecord(metamodelMocks.forOwlClassD().owlClassAAtt(), newValueClone)); when(uow.getOriginal(newValueClone)).thenReturn(newValue); builder.mergeChanges(chSet); assertSame(newValue, entityD.getOwlClassA()); @@ -577,8 +639,9 @@ public void mergeOfFieldOfManagedTypeUsesOriginalValueForMerge() { @Test public void buildCloneClonesMappedSuperclassFieldsAsWell() { - - final OWLClassQ clone = (OWLClassQ) builder.buildClone(entityQ, new CloneConfiguration(defaultDescriptor)); + defaultLoadStateDescriptor(entityQ, metamodelMocks.forOwlClassQ().entityType()); + defaultLoadStateDescriptor(entityA, metamodelMocks.forOwlClassA().entityType()); + final OWLClassQ clone = (OWLClassQ) builder.buildClone(entityQ, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(clone); assertEquals(entityQ.getUri(), clone.getUri()); assertEquals(entityQ.getStringAttribute(), clone.getStringAttribute()); @@ -591,6 +654,7 @@ public void buildCloneClonesMappedSuperclassFieldsAsWell() { @Test public void mergeChangesMergesChangesOnMappedSuperclassFields() { + defaultLoadStateDescriptor(entityQ, metamodelMocks.forOwlClassQ().entityType()); final OWLClassQ qClone = new OWLClassQ(); qClone.setUri(entityQ.getUri()); qClone.setStringAttribute("newStringAtt"); @@ -598,13 +662,13 @@ public void mergeChangesMergesChangesOnMappedSuperclassFields() { final OWLClassA newA = new OWLClassA(); newA.setUri(URI.create("http://krizik.felk.cvut.cz/ontologies/jopa/newA")); qClone.setOwlClassA(newA); - final ObjectChangeSet changeSet = TestEnvironmentUtils.createObjectChangeSet(entityQ, qClone, null); + final ObjectChangeSet changeSet = ChangeSetFactory.createObjectChangeSet(entityQ, qClone, new EntityDescriptor()); changeSet.addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassQ().qStringAtt(), qClone.getStringAttribute())); + new ChangeRecord(metamodelMocks.forOwlClassQ().qStringAtt(), qClone.getStringAttribute())); changeSet.addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassQ().qParentStringAtt(), qClone.getParentString())); + new ChangeRecord(metamodelMocks.forOwlClassQ().qParentStringAtt(), qClone.getParentString())); changeSet.addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassQ().qOwlClassAAtt(), qClone.getOwlClassA())); + new ChangeRecord(metamodelMocks.forOwlClassQ().qOwlClassAAtt(), qClone.getOwlClassA())); builder.mergeChanges(changeSet); assertEquals(qClone.getStringAttribute(), entityQ.getStringAttribute()); @@ -623,14 +687,16 @@ public void buildCloneDoesNotAttemptToRegisterCloneWithoutIdentifier() throws Ex initMetamodelForAB(); doAnswer(invocation -> { Object obj = invocation.getArguments()[0]; - Descriptor desc = (Descriptor) invocation.getArguments()[1]; - final Object clone = builder.buildClone(obj, new CloneConfiguration(desc)); + CloneRegistrationDescriptor desc = (CloneRegistrationDescriptor) invocation.getArguments()[1]; + final Object clone = builder.buildClone(obj, new CloneConfiguration(desc.getDescriptor(), false)); // THIS is the important verification assertNotNull(EntityPropertiesUtils.getIdentifier(clone, metamodel)); return clone; - }).when(uow).registerExistingObject(any(), any(), any()); + }).when(uow).registerExistingObject(any(), any(CloneRegistrationDescriptor.class)); + defaultLoadStateDescriptor(b, metamodel.entity(B.class)); + defaultLoadStateDescriptor(a, metamodel.entity(A.class)); - final B result = (B) builder.buildClone(b, new CloneConfiguration(defaultDescriptor)); + final B result = (B) builder.buildClone(b, new CloneConfiguration(defaultDescriptor, false)); assertNotNull(result); assertNotNull(result.a); assertEquals(result, result.a.b.iterator().next()); @@ -642,7 +708,9 @@ private void initMetamodelForAB() throws Exception { when(uow.isEntityType(A.class)).thenReturn(true); when(uow.isEntityType(B.class)).thenReturn(true); final IdentifiableEntityType etA = mock(IdentifiableEntityType.class); + when(etA.getJavaType()).thenReturn(A.class); final IdentifiableEntityType etB = mock(IdentifiableEntityType.class); + when(etB.getJavaType()).thenReturn(B.class); final Identifier idA = new IRIIdentifierImpl<>(etA, A.class.getDeclaredField("uri"), true); final Identifier idB = new IRIIdentifierImpl<>(etB, B.class.getDeclaredField("uri"), true); when(etA.getIdentifier()).thenReturn(idA); @@ -669,7 +737,7 @@ private void initMetamodelForAB() throws Exception { doReturn(etB).when(metamodel).entity(B.class); } - private static class A { + public static class A { @Id private URI uri; @@ -677,7 +745,7 @@ private static class A { private Set b; } - private static class B { + public static class B { @Id private URI uri; @@ -688,12 +756,68 @@ private static class B { @Test public void buildCloneReturnsSameInstanceForImmutableClasses() { final LocalDateTime instance = LocalDateTime.now(); - assertSame(instance, builder.buildClone(instance, new CloneConfiguration(defaultDescriptor))); + assertSame(instance, builder.buildClone(instance, new CloneConfiguration(defaultDescriptor, false))); } @Test public void buildCloneReturnsSameInstanceForJava8Instant() { final Instant instance = Instant.now(); - assertSame(instance, builder.buildClone(instance, new CloneConfiguration(defaultDescriptor))); + assertSame(instance, builder.buildClone(instance, new CloneConfiguration(defaultDescriptor, false))); + } + + @Test + void buildCloneCreatesAndRegistersLoadStateDescriptorForClone() { + loadStateRegistry.put(entityA, LoadStateDescriptorFactory.createAllLoaded(entityA, metamodelMocks.forOwlClassA() + .entityType())); + final OWLClassA res = (OWLClassA) builder.buildClone(entityA, new CloneConfiguration(defaultDescriptor, true)); + assertTrue(uow.getLoadStateRegistry().contains(res)); + final LoadStateDescriptor originalLoadState = loadStateRegistry.get(entityA); + final LoadStateDescriptor resultLoadState = loadStateRegistry.get(res); + metamodelMocks.forOwlClassA().entityType().getFieldSpecifications() + .forEach(fs -> assertEquals(originalLoadState.isLoaded(fs), resultLoadState.isLoaded(fs))); + } + + @Test + void buildCloneSetsLazyLoadingProxiesForPluralAttributesWithNotLoadedState() { + final LoadStateDescriptor loadState = LoadStateDescriptorFactory.createAllUnknown(entityC, metamodelMocks.forOwlClassC() + .entityType()); + loadStateRegistry.put(entityC, loadState); + loadState.setLoaded(metamodelMocks.forOwlClassC().referencedListAtt(), LoadState.LOADED); + loadState.setLoaded(metamodelMocks.forOwlClassC().simpleListAtt(), LoadState.NOT_LOADED); + + final OWLClassC result = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor, true)); + assertNotNull(result.getSimpleList()); + assertInstanceOf(LazyLoadingProxy.class, result.getSimpleList()); + } + + @Test + void buildCloneSetsLazyLoadingProxiesForSingularAttributesWithNotLoadedState() { + when(metamodelMocks.forOwlClassD().owlClassAAtt().getFetchType()).thenReturn(FetchType.LAZY); + final LoadStateDescriptor loadState = LoadStateDescriptorFactory.createAllUnknown(entityD, metamodelMocks.forOwlClassD() + .entityType()); + loadStateRegistry.put(entityD, loadState); + entityD.setOwlClassA(null); + loadState.setLoaded(metamodelMocks.forOwlClassD().owlClassAAtt(), LoadState.LOADED); + + final OWLClassD result = (OWLClassD) builder.buildClone(entityD, new CloneConfiguration(defaultDescriptor, true)); + assertNull(result.getOwlClassA()); + } + + @Test + public void mergeChangesUpdatesLoadStateOfOriginalLoadStateDescriptor() { + final LoadStateDescriptor loadStateDescriptor = LoadStateDescriptorFactory.createNotLoaded(entityC, metamodelMocks.forOwlClassC() + .entityType()); + loadStateDescriptor.setLoaded(metamodelMocks.forOwlClassC().referencedListAtt(), LoadState.LOADED); + loadStateRegistry.put(entityC, loadStateDescriptor); + final OWLClassC c = (OWLClassC) builder.buildClone(entityC, new CloneConfiguration(defaultDescriptor, false)); + assertNotSame(entityC, c); + c.setSimpleList(null); + final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(entityC, c, defaultDescriptor); + chSet.addChangeRecord(new ChangeRecord(metamodelMocks.forOwlClassC().simpleListAtt(), + c.getSimpleList())); + builder.mergeChanges(chSet); + + assertNull(entityC.getSimpleList()); + assertEquals(LoadState.LOADED, loadStateDescriptor.isLoaded(metamodelMocks.forOwlClassC().simpleListAtt())); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/CollectionInstanceBuilderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/CollectionInstanceBuilderTest.java index c56ce58a5..e9db72027 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/CollectionInstanceBuilderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/CollectionInstanceBuilderTest.java @@ -17,8 +17,8 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectList; -import cz.cvut.kbss.jopa.adapters.IndirectSet; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectList; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectSet; import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.OWLClassC; import cz.cvut.kbss.jopa.environment.OWLClassJ; @@ -28,6 +28,8 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.CollectionType; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; +import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; import cz.cvut.kbss.jopa.utils.CollectionFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -51,7 +53,7 @@ public class CollectionInstanceBuilderTest { @Mock - private UnitOfWorkImpl uowMock; + private AbstractUnitOfWork uowMock; private Descriptor descriptor; @@ -59,7 +61,7 @@ public class CollectionInstanceBuilderTest { @BeforeEach public void setUp() { - CloneBuilderImpl cloneBuilder = new CloneBuilderImpl(uowMock); + CloneBuilder cloneBuilder = new CloneBuilder(uowMock); this.builder = new CollectionInstanceBuilder(cloneBuilder, uowMock); this.descriptor = new EntityDescriptor(); } @@ -70,9 +72,9 @@ private void mockIndirectCollectionBuilder() { final Object owner = invocation.getArguments()[1]; final Field field = (Field) invocation.getArguments()[2]; if (col instanceof List) { - return new IndirectList<>(owner, field, uowMock, (List) col); + return new ChangeTrackingIndirectList<>(owner, field, uowMock, (List) col); } else { - return new IndirectSet<>(owner, field, uowMock, (Set) col); + return new ChangeTrackingIndirectSet<>(owner, field, uowMock, (Set) col); } }); } @@ -84,9 +86,9 @@ public void buildCloneCreatesDefaultListWhenUnknownListImplementationIsPassedAsA owner.list = new TestList<>(owner); IntStream.range(0, 10).forEach(i -> owner.list.add("String" + i)); final Object result = - builder.buildClone(owner, CollectionOwner.listField(), owner.list, new CloneConfiguration(descriptor)); + builder.buildClone(owner, CollectionOwner.listField(), owner.list, new CloneConfiguration(descriptor, false)); assertNotNull(result); - assertTrue(result instanceof List); + assertInstanceOf(List.class, result); final List lstResult = (List) result; owner.list.forEach(e -> assertTrue(lstResult.contains(e))); } @@ -98,9 +100,9 @@ public void buildCloneCreatesDefaultSetWhenUnknownSetImplementationIsPassedAsArg owner.set = new TestSet<>(owner); IntStream.range(0, 10).forEach(i -> owner.set.add("String" + i)); final Object result = - builder.buildClone(owner, CollectionOwner.setField(), owner.set, new CloneConfiguration(descriptor)); + builder.buildClone(owner, CollectionOwner.setField(), owner.set, new CloneConfiguration(descriptor, false)); assertNotNull(result); - assertTrue(result instanceof Set); + assertInstanceOf(Set.class, result); final Set setResult = (Set) result; owner.set.forEach(e -> assertTrue(setResult.contains(e))); } @@ -110,7 +112,7 @@ public void buildCloneThrowsUnsupportedCollectionTypeExceptionWhenCollectionIsNe final CollectionOwner owner = new CollectionOwner(); owner.queue = new TestQueue<>(owner); final OWLPersistenceException ex = assertThrows(OWLPersistenceException.class, () -> builder - .buildClone(owner, CollectionOwner.queueField(), owner.queue, new CloneConfiguration(descriptor))); + .buildClone(owner, CollectionOwner.queueField(), owner.queue, new CloneConfiguration(descriptor, false))); assertThat(ex.getMessage(), containsString("Cannot clone unsupported collection instance of type " + owner.queue.getClass())); } @@ -173,13 +175,27 @@ public void buildingSingletonSetCloneRegistersElementCloneInUoW() throws Excepti final OWLClassA aOrig = Generators.generateOwlClassAInstance(); final OWLClassA aClone = new OWLClassA(aOrig); owner.setOwlClassA(Collections.singleton(aOrig)); - when(uowMock.registerExistingObject(aOrig, descriptor, Collections.emptyList())).thenReturn(aClone); + when(uowMock.registerExistingObject(aOrig, new CloneRegistrationDescriptor(descriptor))).thenReturn(aClone); when(uowMock.isEntityType(OWLClassA.class)).thenReturn(true); final Set clone = (Set) builder.buildClone(owner, OWLClassJ.getOwlClassAField(), owner.getOwlClassA(), - new CloneConfiguration(descriptor)); + new CloneConfiguration(descriptor, false)); assertEquals(owner.getOwlClassA().size(), clone.size()); assertSame(aClone, clone.iterator().next()); - verify(uowMock).registerExistingObject(aOrig, descriptor, Collections.emptyList()); + verify(uowMock).registerExistingObject(aOrig, new CloneRegistrationDescriptor(descriptor)); + } + + @Test + void buildingSingletonSetReturnsRegularSet() throws Exception { + mockIndirectCollectionBuilder(); + final OWLClassJ owner = new OWLClassJ(Generators.createIndividualIdentifier()); + final OWLClassA aOrig = Generators.generateOwlClassAInstance(); + final OWLClassA aClone = new OWLClassA(aOrig); + owner.setOwlClassA(Collections.singleton(aOrig)); + when(uowMock.registerExistingObject(aOrig, new CloneRegistrationDescriptor(descriptor))).thenReturn(aClone); + when(uowMock.isEntityType(OWLClassA.class)).thenReturn(true); + final Set clone = (Set) builder.buildClone(owner, OWLClassJ.getOwlClassAField(), owner.getOwlClassA(), + new CloneConfiguration(descriptor, false)); + assertDoesNotThrow(() -> clone.add(Generators.generateOwlClassAInstance())); } @Test @@ -189,14 +205,44 @@ public void buildCloneClonesSingletonListWithContent() throws Exception { final OWLClassA aOrig = Generators.generateOwlClassAInstance(); final OWLClassA aClone = new OWLClassA(aOrig); owner.setSimpleList(Collections.singletonList(aOrig)); - when(uowMock.registerExistingObject(aOrig, descriptor, Collections.emptyList())).thenReturn(aClone); + when(uowMock.registerExistingObject(aOrig, new CloneRegistrationDescriptor(descriptor))).thenReturn(aClone); when(uowMock.isEntityType(OWLClassA.class)).thenReturn(true); final List clone = (List) builder.buildClone(owner, OWLClassC.getSimpleListField(), owner.getSimpleList(), - new CloneConfiguration(descriptor)); + new CloneConfiguration(descriptor, false)); assertEquals(1, clone.size()); assertSame(aClone, clone.get(0)); - verify(uowMock).registerExistingObject(aOrig, descriptor, Collections.emptyList()); + verify(uowMock).registerExistingObject(aOrig, new CloneRegistrationDescriptor(descriptor)); + } + + @Test + public void buildCloneOfSingletonListReturnsRegularList() throws Exception { + mockIndirectCollectionBuilder(); + final OWLClassC owner = new OWLClassC(Generators.createIndividualIdentifier()); + final OWLClassA aOrig = Generators.generateOwlClassAInstance(); + final OWLClassA aClone = new OWLClassA(aOrig); + owner.setSimpleList(Collections.singletonList(aOrig)); + when(uowMock.registerExistingObject(aOrig, new CloneRegistrationDescriptor(descriptor))).thenReturn(aClone); + when(uowMock.isEntityType(OWLClassA.class)).thenReturn(true); + + final List clone = (List) builder.buildClone(owner, OWLClassC.getSimpleListField(), owner.getSimpleList(), + new CloneConfiguration(descriptor, false)); + assertDoesNotThrow(() -> clone.add(Generators.generateOwlClassAInstance())); + } + + @Test + public void buildCloneOfListOfInstanceReturnsRegularList() throws Exception { + mockIndirectCollectionBuilder(); + final OWLClassC owner = new OWLClassC(Generators.createIndividualIdentifier()); + final OWLClassA aOrig = Generators.generateOwlClassAInstance(); + final OWLClassA aClone = new OWLClassA(aOrig); + owner.setSimpleList(List.of(aOrig)); + when(uowMock.registerExistingObject(aOrig, new CloneRegistrationDescriptor(descriptor))).thenReturn(aClone); + when(uowMock.isEntityType(OWLClassA.class)).thenReturn(true); + + final List clone = (List) builder.buildClone(owner, OWLClassC.getSimpleListField(), owner.getSimpleList(), + new CloneConfiguration(descriptor, false)); + assertDoesNotThrow(() -> clone.add(Generators.generateOwlClassAInstance())); } @Test @@ -208,17 +254,17 @@ public void buildCloneClonesArrayAsListWithContent() throws Exception { final OWLClassA aTwoOrig = Generators.generateOwlClassAInstance(); final OWLClassA aTwoClone = new OWLClassA(aTwoOrig); owner.setSimpleList(Arrays.asList(aOneOrig, aTwoOrig)); - when(uowMock.registerExistingObject(aOneOrig, descriptor, Collections.emptyList())).thenReturn(aOneClone); - when(uowMock.registerExistingObject(aTwoOrig, descriptor, Collections.emptyList())).thenReturn(aTwoClone); + when(uowMock.registerExistingObject(aOneOrig, new CloneRegistrationDescriptor(descriptor))).thenReturn(aOneClone); + when(uowMock.registerExistingObject(aTwoOrig, new CloneRegistrationDescriptor(descriptor))).thenReturn(aTwoClone); when(uowMock.isEntityType(OWLClassA.class)).thenReturn(true); final List clone = (List) builder.buildClone(owner, OWLClassC.getSimpleListField(), owner.getSimpleList(), - new CloneConfiguration(descriptor)); + new CloneConfiguration(descriptor, false)); assertEquals(2, clone.size()); assertSame(aOneClone, clone.get(0)); assertSame(aTwoClone, clone.get(1)); - verify(uowMock).registerExistingObject(aOneOrig, descriptor, Collections.emptyList()); - verify(uowMock).registerExistingObject(aTwoOrig, descriptor, Collections.emptyList()); + verify(uowMock).registerExistingObject(aOneOrig, new CloneRegistrationDescriptor(descriptor)); + verify(uowMock).registerExistingObject(aTwoOrig, new CloneRegistrationDescriptor(descriptor)); } @Test diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapperTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapperTest.java index 6730561d5..7071c4af4 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapperTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapperTest.java @@ -70,4 +70,4 @@ void throwsPersistenceExceptionWhenUnwrapCallFailsOnConnection() throws Exceptio when(connectionMock.unwrap(Object.class)).thenThrow(new OntoDriverException()); assertThrows(OWLPersistenceException.class, () -> connectionWrapper.unwrap(Object.class)); } -} \ No newline at end of file +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/DateInstanceBuilderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/DateInstanceBuilderTest.java index ad27620f7..b8e4566a8 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/DateInstanceBuilderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/DateInstanceBuilderTest.java @@ -20,6 +20,7 @@ import cz.cvut.kbss.jopa.environment.OWLClassM; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,8 +36,7 @@ class DateInstanceBuilderTest { private Field dateField; private Descriptor descriptor; - private final DateInstanceBuilder builder = new DateInstanceBuilder(mock(CloneBuilderImpl.class), - mock(UnitOfWorkImpl.class)); + private final DateInstanceBuilder builder = new DateInstanceBuilder(mock(CloneBuilder.class), mock(UnitOfWork.class)); @BeforeEach void setUp() throws Exception { @@ -48,15 +48,15 @@ void setUp() throws Exception { @Test void testBuildClone() { final Date original = new Date(); - final Object res = builder.buildClone(entityM, dateField, original, new CloneConfiguration(descriptor)); - assertTrue(res instanceof Date); + final Object res = builder.buildClone(entityM, dateField, original, new CloneConfiguration(descriptor, false)); + assertInstanceOf(Date.class, res); assertNotSame(original, res); assertEquals(original, res); } @Test void testBuildCloneOfNull() { - final Object res = builder.buildClone(entityM, dateField, null, new CloneConfiguration(descriptor)); + final Object res = builder.buildClone(entityM, dateField, null, new CloneConfiguration(descriptor, false)); assertNull(res); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/DetachedInstanceMergerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/DetachedInstanceMergerTest.java index e1d187539..ce1652bd9 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/DetachedInstanceMergerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/DetachedInstanceMergerTest.java @@ -24,8 +24,9 @@ import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl; -import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSetImpl; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -36,8 +37,13 @@ import java.util.HashSet; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +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.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -46,7 +52,7 @@ public class DetachedInstanceMergerTest { private final Descriptor descriptor = new EntityDescriptor(); @Mock - private UnitOfWorkImpl uow; + private UnitOfWork uow; private MetamodelMocks metamodelMocks; @@ -70,7 +76,7 @@ public void mergeFromDetachedAssignsValuesOfNonManagedTypes() { final String updatedString = "updatedString"; detached.setStringAttribute(updatedString); final ObjectChangeSet changeSet = createChangeSet(original, detached); - changeSet.addChangeRecord(new ChangeRecordImpl(metamodelMocks.forOwlClassA().stringAttribute(), updatedString)); + changeSet.addChangeRecord(new ChangeRecord(metamodelMocks.forOwlClassA().stringAttribute(), updatedString)); final OWLClassA result = (OWLClassA) sut.mergeChangesFromDetachedToManagedInstance(changeSet, descriptor); assertEquals(updatedString, result.getStringAttribute()); @@ -82,7 +88,7 @@ public void mergeFromDetachedSetsValueToNullWhenChangeValueIsNull() { final OWLClassA detached = new OWLClassA(original.getUri()); detached.setTypes(original.getTypes()); final ObjectChangeSet changeSet = createChangeSet(original, detached); - changeSet.addChangeRecord(new ChangeRecordImpl(metamodelMocks.forOwlClassA().stringAttribute(), null)); + changeSet.addChangeRecord(new ChangeRecord(metamodelMocks.forOwlClassA().stringAttribute(), null)); final OWLClassA result = (OWLClassA) sut.mergeChangesFromDetachedToManagedInstance(changeSet, descriptor); assertNull(result.getStringAttribute()); @@ -99,7 +105,7 @@ public void mergeFromDetachedLoadsExistingInstanceCorrespondingToNewValue() { newRefOrig.setStringAttribute(newRef.getStringAttribute()); newRefOrig.setTypes(new HashSet<>(newRef.getTypes())); final ObjectChangeSet chSet = createChangeSet(orig, clone); - chSet.addChangeRecord(new ChangeRecordImpl(metamodelMocks.forOwlClassD().owlClassAAtt(), newRef)); + chSet.addChangeRecord(new ChangeRecord(metamodelMocks.forOwlClassD().owlClassAAtt(), newRef)); when(uow.readObject(OWLClassA.class, newRef.getUri(), descriptor)).thenReturn(newRefOrig); when(uow.isEntityType(OWLClassA.class)).thenReturn(true); @@ -110,6 +116,6 @@ public void mergeFromDetachedLoadsExistingInstanceCorrespondingToNewValue() { } private ObjectChangeSet createChangeSet(Object original, Object clone) { - return new ObjectChangeSetImpl(original, clone, new EntityDescriptor()); + return ChangeSetFactory.createObjectChangeSet(original, clone, new EntityDescriptor()); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/IndirectWrapperHelperTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/IndirectWrapperHelperTest.java new file mode 100644 index 000000000..64558ffff --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/IndirectWrapperHelperTest.java @@ -0,0 +1,26 @@ +package cz.cvut.kbss.jopa.sessions; + +import cz.cvut.kbss.jopa.environment.OWLClassJ; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingSetProxy; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +class IndirectWrapperHelperTest { + + @Mock + private UnitOfWork uow; + + @Test + void requiresIndirectWrapperReturnsFalseForLazyLoadingCollectionProxies() { + final OWLClassJ owner = new OWLClassJ(Generators.createIndividualIdentifier()); + assertFalse(IndirectWrapperHelper.requiresIndirectWrapper(new LazyLoadingSetProxy<>(owner, mock(FieldSpecification.class), uow))); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/MergeManagerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/MergeManagerTest.java index e6ae087d8..f1c297555 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/MergeManagerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/MergeManagerTest.java @@ -25,20 +25,31 @@ import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory; -import cz.cvut.kbss.jopa.sessions.change.UnitOfWorkChangeSetImpl; -import org.junit.jupiter.api.AfterEach; +import cz.cvut.kbss.jopa.sessions.change.Change; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; +import cz.cvut.kbss.jopa.sessions.change.UnitOfWorkChangeSet; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; 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.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import java.net.URI; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) class MergeManagerTest { private static final URI DEFAULT_URI = URI.create("http://defaultContext"); @@ -46,10 +57,10 @@ class MergeManagerTest { private static Descriptor defaultDescriptor; @Mock - private UnitOfWorkImpl uow; + private AbstractUnitOfWork uow; @Mock - private CloneBuilderImpl cloneBuilder; + private CloneBuilder cloneBuilder; @Mock private MetamodelImpl metamodel; @@ -58,7 +69,7 @@ class MergeManagerTest { private UnitOfWorkChangeSet uowChangeSet; - private MergeManagerImpl mm; + private MergeManager mm; @BeforeAll static void setUpBeforeClass() { @@ -67,18 +78,11 @@ static void setUpBeforeClass() { @BeforeEach void setUp() throws Exception { - MockitoAnnotations.openMocks(this); - this.uowChangeSet = new UnitOfWorkChangeSetImpl(); + this.uowChangeSet = ChangeSetFactory.createUoWChangeSet(); when(uow.getMetamodel()).thenReturn(metamodel); - when(uow.getCloneBuilder()).thenReturn(cloneBuilder); this.metamodelMocks = new MetamodelMocks(); metamodelMocks.setMocks(metamodel); - this.mm = new MergeManagerImpl(uow); - } - - @AfterEach - void tearDown() { - uow.release(); + this.mm = new MergeManager(uow, cloneBuilder); } @Test @@ -89,7 +93,7 @@ void mergeChangesOnObjectCallsCloneBuilderWithChangeSetToMerge() { final ObjectChangeSet chs = createChangeSet(orig, clone); clone.setStringAttribute("AnotherStringAttribute"); chs.addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassB().stringAttribute(), clone.getStringAttribute())); + new ChangeRecord(metamodelMocks.forOwlClassB().stringAttribute(), clone.getStringAttribute())); mm.mergeChangesOnObject(chs); verify(cloneBuilder).mergeChanges(chs); } @@ -101,10 +105,10 @@ void testMergeChangesFromChangeSet() { OWLClassB cloneOne = new OWLClassB(objOne.getUri()); OWLClassB cloneTwo = new OWLClassB(objTwo.getUri()); cloneOne.setStringAttribute("testAtt"); - uowChangeSet.addDeletedObjectChangeSet(createChangeSet(objTwo, cloneTwo)); + uowChangeSet.addDeletedObjectChangeSet(ChangeSetFactory.createDeleteObjectChange(cloneTwo, objTwo, defaultDescriptor)); final ObjectChangeSet changeSet = createChangeSet(objOne, cloneOne); changeSet.addChangeRecord( - new ChangeRecordImpl(metamodelMocks.forOwlClassB().stringAttribute(), cloneOne.getStringAttribute())); + new ChangeRecord(metamodelMocks.forOwlClassB().stringAttribute(), cloneOne.getStringAttribute())); uowChangeSet.addObjectChangeSet(changeSet); mm.mergeChangesFromChangeSet(uowChangeSet); verify(uow).removeObjectFromCache(objTwo, DEFAULT_URI); @@ -112,12 +116,13 @@ void testMergeChangesFromChangeSet() { } @Test - void mergeChangesFromChangeSetWithNewObjectPutsOriginalIntoCache() { + void mergeChangesFromUoWChangeSetWithNewObjectPutsOriginalIntoCache() { final OWLClassB objOne = new OWLClassB(Generators.createIndividualIdentifier()); objOne.setStringAttribute("ABeautifulAttribute"); final OWLClassB clone = new OWLClassB(objOne.getUri()); - final ObjectChangeSet changeSet = createChangeSet(objOne, clone); - uowChangeSet.addNewObjectChangeSet(changeSet); + clone.setStringAttribute(objOne.getStringAttribute()); + when(cloneBuilder.buildClone(eq(clone), any(CloneConfiguration.class))).thenReturn(objOne); + uowChangeSet.addNewObjectChangeSet(ChangeSetFactory.createNewObjectChange(clone, defaultDescriptor)); mm.mergeChangesFromChangeSet(uowChangeSet); verify(uow).putObjectIntoCache(objOne.getUri(), objOne, defaultDescriptor); } @@ -126,7 +131,8 @@ void mergeChangesFromChangeSetWithNewObjectPutsOriginalIntoCache() { void mergeNewObjectPutsObjectIntoCache() { final OWLClassB newOne = new OWLClassB(Generators.createIndividualIdentifier()); final OWLClassB clone = new OWLClassB(newOne.getUri()); - final ObjectChangeSet changeSet = createChangeSet(newOne, clone); + when(cloneBuilder.buildClone(eq(clone), any(CloneConfiguration.class))).thenReturn(newOne); + final Change changeSet = ChangeSetFactory.createNewObjectChange(clone, defaultDescriptor); mm.mergeNewObject(changeSet); verify(uow).putObjectIntoCache(newOne.getUri(), newOne, defaultDescriptor); } @@ -141,8 +147,8 @@ void mergeChangesOnObjectPreventsCachingWhenChangeRecordPreventingCachingExists( final OWLClassD clone = new OWLClassD(original.getUri()); clone.setOwlClassA(new OWLClassA(Generators.createIndividualIdentifier())); final ObjectChangeSet changeSet = createChangeSet(original, clone); - final ChangeRecordImpl record = - new ChangeRecordImpl(metamodelMocks.forOwlClassD().owlClassAAtt(), clone.getOwlClassA()); + final ChangeRecord record = + new ChangeRecord(metamodelMocks.forOwlClassD().owlClassAAtt(), clone.getOwlClassA()); record.preventCaching(); changeSet.addChangeRecord(record); mm.mergeChangesOnObject(changeSet); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/MultilingualStringInstanceBuilderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/MultilingualStringInstanceBuilderTest.java index 9b0d969ae..6f9db30f8 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/MultilingualStringInstanceBuilderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/MultilingualStringInstanceBuilderTest.java @@ -17,12 +17,13 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectMultilingualString; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectMultilingualString; import cz.cvut.kbss.jopa.environment.OWLClassU; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.model.MultilingualString; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; +import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,7 +41,7 @@ class MultilingualStringInstanceBuilderTest { @BeforeEach void setUp() { - this.sut = new MultilingualStringInstanceBuilder(mock(CloneBuilderImpl.class), mock(UnitOfWorkImpl.class)); + this.sut = new MultilingualStringInstanceBuilder(mock(CloneBuilder.class), mock(UnitOfWork.class)); } @Test @@ -48,7 +49,7 @@ void buildCloneCopiesAllTranslationsInSpecifiedOriginalMultilingualString() thro final MultilingualString original = MultilingualString.create("building", Generators.LANG); original.set("cs", "stavba"); final Object result = sut.buildClone(new OWLClassU(), OWLClassU.getSingularStringAttField(), original, - new CloneConfiguration(descriptor)); + new CloneConfiguration(descriptor, false)); assertThat(result, instanceOf(MultilingualString.class)); final MultilingualString typedResult = (MultilingualString) result; assertEquals(original.getValue(), typedResult.getValue()); @@ -57,7 +58,7 @@ void buildCloneCopiesAllTranslationsInSpecifiedOriginalMultilingualString() thro @Test void buildCloneResultsNullWhenOriginalIsNull() throws Exception { final Object result = sut.buildClone(new OWLClassU(), OWLClassU.getSingularStringAttField(), null, - new CloneConfiguration(descriptor)); + new CloneConfiguration(descriptor, false)); assertNull(result); } @@ -66,18 +67,18 @@ void buildCloneReturnsIndirectWrapperAllowingToTrackModifyingOperations() throws final MultilingualString original = MultilingualString.create("building", Generators.LANG); original.set("cs", "stavba"); final Object result = sut.buildClone(new OWLClassU(), OWLClassU.getSingularStringAttField(), original, - new CloneConfiguration(descriptor)); - assertThat(result, instanceOf(IndirectMultilingualString.class)); + new CloneConfiguration(descriptor, false)); + assertThat(result, instanceOf(ChangeTrackingIndirectMultilingualString.class)); } @Test void buildCloneBuildsCloneOfWrappedMultilingualStringWhenArgumentIsIndirectMultilingualString() throws Exception { final MultilingualString original = MultilingualString.create("building", Generators.LANG); - final IndirectMultilingualString arg = new IndirectMultilingualString(new OWLClassU(), - OWLClassU.getSingularStringAttField(), mock(UnitOfWorkImpl.class), original); - final Object result = sut.buildClone(new OWLClassU(), OWLClassU.getSingularStringAttField(), arg, new CloneConfiguration(descriptor)); - assertThat(result, instanceOf(IndirectMultilingualString.class)); - final IndirectMultilingualString resultIndirect = (IndirectMultilingualString) result; + final ChangeTrackingIndirectMultilingualString arg = new ChangeTrackingIndirectMultilingualString(new OWLClassU(), + OWLClassU.getSingularStringAttField(), mock(UnitOfWork.class), original); + final Object result = sut.buildClone(new OWLClassU(), OWLClassU.getSingularStringAttField(), arg, new CloneConfiguration(descriptor, false)); + assertThat(result, instanceOf(ChangeTrackingIndirectMultilingualString.class)); + final ChangeTrackingIndirectMultilingualString resultIndirect = (ChangeTrackingIndirectMultilingualString) result; assertEquals(original, resultIndirect.unwrap()); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWorkMergeTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWorkMergeTest.java new file mode 100644 index 000000000..69dd28a15 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWorkMergeTest.java @@ -0,0 +1,57 @@ +package cz.cvut.kbss.jopa.sessions; + +import cz.cvut.kbss.jopa.environment.NoopInstantiableTypeGenerator; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.utils.MetamodelFactory; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; +import cz.cvut.kbss.jopa.utils.Configuration; +import org.junit.jupiter.api.AfterAll; +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class OnCommitChangePropagatingUnitOfWorkMergeTest extends UnitOfWorkMergeTestRunner { + + @BeforeAll + static void setUpBeforeAll() { + MetamodelFactory.setInstantiableTypeGenerator(NoopInstantiableTypeGenerator.INSTANCE); + } + + @BeforeEach + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected AbstractUnitOfWork initUnitOfWork() { + return new OnCommitChangePropagatingUnitOfWork(serverSessionStub, new Configuration()); + } + + @AfterAll + static void tearDownAfterAll() { + MetamodelFactory.reset(); + } + + @Test + void testMergeDetachedExisting() { + final OWLClassA result = mergeDetached(); + assertNotNull(result); + assertEquals(entityA.getUri(), result.getUri()); + assertEquals(entityA.getStringAttribute(), result.getStringAttribute()); + final ObjectChangeSet changeSet = uow.uowChangeSet.getExistingObjectChanges(uow.getOriginal(result)); + assertNotNull(changeSet); + assertTrue(changeSet.getChanges().stream() + .anyMatch(r -> r.getAttribute().equals(metamodelMocks.forOwlClassA().stringAttribute()))); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWorkTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWorkTest.java new file mode 100644 index 000000000..c6f69c1ae --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWorkTest.java @@ -0,0 +1,132 @@ +package cz.cvut.kbss.jopa.sessions; + +import cz.cvut.kbss.jopa.environment.NoopInstantiableTypeGenerator; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassM; +import cz.cvut.kbss.jopa.environment.OWLClassU; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.environment.utils.MetamodelFactory; +import cz.cvut.kbss.jopa.exceptions.AttributeModificationForbiddenException; +import cz.cvut.kbss.jopa.model.MultilingualString; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; +import cz.cvut.kbss.jopa.utils.Configuration; +import org.junit.jupiter.api.AfterAll; +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class OnCommitChangePropagatingUnitOfWorkTest extends AbstractUnitOfWorkTestRunner { + + @BeforeAll + static void setUpBeforeAll() { + MetamodelFactory.setInstantiableTypeGenerator(NoopInstantiableTypeGenerator.INSTANCE); + } + + @BeforeEach + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected AbstractUnitOfWork initUnitOfWork() { + return new OnCommitChangePropagatingUnitOfWork(serverSessionStub, new Configuration()); + } + + @AfterAll + static void tearDownAfterAll() { + MetamodelFactory.reset(); + } + + @Test + void commitToStorageCalculatesChangesToExistingObjectsAndPropagatesThemToStorage() { + defaultLoadStateDescriptor(entityA); + final OWLClassA clone = (OWLClassA) uow.registerExistingObject(entityA, descriptor); + clone.setStringAttribute("new string value"); + assertTrue(uow.uowChangeSet.getExistingObjectsChanges().isEmpty()); + uow.commit(); + verify(storageMock).merge(clone, metamodelMocks.forOwlClassA().stringAttribute(), descriptor); + } + + @Test + void commitToStorageDeletesRemovedObjectsFromStorage() { + defaultLoadStateDescriptor(entityA); + final OWLClassA clone = (OWLClassA) uow.registerExistingObject(entityA, descriptor); + uow.removeObject(clone); + verify(storageMock, never()).remove(clone.getUri(), OWLClassA.class, descriptor); + uow.commit(); + verify(storageMock).remove(clone.getUri(), OWLClassA.class, descriptor); + } + + @Test + void removeObjectRegistersObjectForDeletion() { + defaultLoadStateDescriptor(entityA); + final OWLClassA clone = (OWLClassA) uow.registerExistingObject(entityA, descriptor); + uow.removeObject(clone); + verify(storageMock, never()).remove(clone.getUri(), OWLClassA.class, descriptor); + assertTrue(uow.deletedObjects.containsKey(clone)); + } + + @Test + void mergeDetachedRegistersChangesToSpecifiedObjectAndReturnsManagedClone() { + when(storageMock.contains(entityA.getUri(), OWLClassA.class, descriptor)).thenReturn(true); + when(storageMock.find(any(LoadingParameters.class))).thenReturn(entityA); + defaultLoadStateDescriptor(entityA); + final OWLClassA toMerge = new OWLClassA(entityA.getUri()); + toMerge.setStringAttribute("Different string"); + toMerge.setTypes(Generators.generateTypes(2)); + + final OWLClassA result = uow.mergeDetached(toMerge, descriptor); + assertNotNull(result); + assertEquals(entityA.getUri(), result.getUri()); + assertEquals(toMerge.getStringAttribute(), result.getStringAttribute()); + assertEquals(toMerge.getTypes(), result.getTypes()); + assertTrue(uow.contains(result)); + assertTrue(uow.uowChangeSet.hasChanges()); + final ObjectChangeSet changeSet = uow.uowChangeSet.getExistingObjectChanges(uow.getOriginal(result)); + assertTrue(changeSet.getChanges().stream() + .anyMatch(r -> r.getAttribute().equals(metamodelMocks.forOwlClassA().stringAttribute()))); + assertTrue(changeSet.getChanges().stream() + .anyMatch(r -> r.getAttribute().equals(metamodelMocks.forOwlClassA().typesSpec()))); + verify(storageMock, never()).merge(any(), any(), any()); + } + + @Test + void commitThrowsAttributeModificationForbiddenExceptionWhenChangeConcernsLexicalValueAttribute() { + final OWLClassM original = new OWLClassM(); + original.initializeTestValues(true); + defaultLoadStateDescriptor(original); + final OWLClassM clone = (OWLClassM) uow.registerExistingObject(original, descriptor); + clone.setLexicalForm("Cannot change"); + assertThrows(AttributeModificationForbiddenException.class, () -> uow.commit()); + } + + @Test + void commitRegistersChangesDoneByPreUpdateCallback() { + final OWLClassU original = new OWLClassU(Generators.createIndividualIdentifier()); + when(storageMock.contains(original.getId(), OWLClassU.class, descriptor)).thenReturn(true); + when(storageMock.find(any(LoadingParameters.class))).thenReturn(original); + defaultLoadStateDescriptor(original); + final OWLClassU toMerge = new OWLClassU(original.getId()); + toMerge.setSingularStringAtt(MultilingualString.create("Test", "en")); + final OWLClassU merged = uow.mergeDetached(toMerge, descriptor); + uow.commit(); + verify(storageMock).merge(merged, metamodelMocks.forOwlClassU().uModified(), descriptor); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/RefreshInstanceMergerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/RefreshInstanceMergerTest.java index 5745fe46e..18e48afeb 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/RefreshInstanceMergerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/RefreshInstanceMergerTest.java @@ -17,9 +17,6 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.adapters.IndirectCollection; -import cz.cvut.kbss.jopa.adapters.IndirectList; -import cz.cvut.kbss.jopa.adapters.IndirectSet; import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.OWLClassC; import cz.cvut.kbss.jopa.environment.utils.Generators; @@ -27,8 +24,12 @@ 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 cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectCollection; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectList; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectSet; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -43,7 +44,7 @@ import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -51,7 +52,7 @@ public class RefreshInstanceMergerTest { @Mock - private UnitOfWorkImpl uowMock; + private UnitOfWork uowMock; private RefreshInstanceMerger sut; @@ -69,7 +70,7 @@ public void mergeChangesOverwritesSingularAttributeChangesWithSourceValues() thr final ObjectChangeSet changeSet = ChangeSetFactory.createObjectChangeSet(original, clone, new EntityDescriptor()); final FieldSpecification fieldSpec = mock(FieldSpecification.class); when(fieldSpec.getJavaField()).thenReturn(OWLClassA.getStrAttField()); - changeSet.addChangeRecord(new ChangeRecordImpl(fieldSpec, clone.getStringAttribute())); + changeSet.addChangeRecord(new ChangeRecord(fieldSpec, clone.getStringAttribute())); sut.mergeChanges(changeSet); assertEquals(original.getStringAttribute(), clone.getStringAttribute()); @@ -78,18 +79,18 @@ public void mergeChangesOverwritesSingularAttributeChangesWithSourceValues() thr @Test public void mergeChangesReplacesCollectionWithNewOneWithSourceValues() throws Exception { final OWLClassA original = Generators.generateOwlClassAInstance(); - original.setTypes(new IndirectSet<>(original, OWLClassA.getTypesField(), uowMock, original.getTypes())); + original.setTypes(new ChangeTrackingIndirectSet<>(original, OWLClassA.getTypesField(), uowMock, original.getTypes())); final OWLClassA clone = new OWLClassA(original.getUri()); clone.setTypes(new HashSet<>(original.getTypes())); final ObjectChangeSet changeSet = ChangeSetFactory.createObjectChangeSet(original, clone, new EntityDescriptor()); final TypesSpecification fieldSpec = mock(TypesSpecification.class); when(fieldSpec.getJavaField()).thenReturn(OWLClassA.getTypesField()); - changeSet.addChangeRecord(new ChangeRecordImpl(fieldSpec, clone.getTypes())); + changeSet.addChangeRecord(new ChangeRecord(fieldSpec, clone.getTypes())); sut.mergeChanges(changeSet); - assertTrue(clone.getTypes() instanceof IndirectSet); + assertInstanceOf(ChangeTrackingIndirectSet.class, clone.getTypes()); assertEquals(original.getTypes(), clone.getTypes()); - final Field ownerField = IndirectCollection.class.getDeclaredField("owner"); + final Field ownerField = ChangeTrackingIndirectCollection.class.getDeclaredField("owner"); ownerField.setAccessible(true); assertEquals(clone, ownerField.get(clone.getTypes())); } @@ -101,18 +102,18 @@ public void mergeChangesReplacesObjectPropertyCollectionWithSourceValues() throw final List refList = IntStream.range(0, 5).mapToObj(i -> Generators.generateOwlClassAInstance()) .collect(Collectors.toList()); final List refListClone = new ArrayList<>(refList); - original.setReferencedList(new IndirectList<>(original, OWLClassC.getRefListField(), uowMock, refList)); - clone.setReferencedList(new IndirectList<>(clone, OWLClassC.getRefListField(), uowMock, refListClone)); + original.setReferencedList(new ChangeTrackingIndirectList<>(original, OWLClassC.getRefListField(), uowMock, refList)); + clone.setReferencedList(new ChangeTrackingIndirectList<>(clone, OWLClassC.getRefListField(), uowMock, refListClone)); clone.getReferencedList().add(Generators.generateOwlClassAInstance()); final Attribute att = mock(Attribute.class); when(att.getJavaField()).thenReturn(OWLClassC.getRefListField()); final ObjectChangeSet changeSet = ChangeSetFactory.createObjectChangeSet(original, clone, new EntityDescriptor()); - changeSet.addChangeRecord(new ChangeRecordImpl(att, refListClone)); + changeSet.addChangeRecord(new ChangeRecord(att, refListClone)); sut.mergeChanges(changeSet); assertEquals(refList.size(), clone.getReferencedList().size()); - assertTrue(clone.getReferencedList() instanceof IndirectList); - final Field ownerField = IndirectCollection.class.getDeclaredField("owner"); + assertInstanceOf(ChangeTrackingIndirectList.class, clone.getReferencedList()); + final Field ownerField = ChangeTrackingIndirectCollection.class.getDeclaredField("owner"); ownerField.setAccessible(true); assertEquals(clone, ownerField.get(clone.getReferencedList())); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ServerSessionStub.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ServerSessionStub.java index 35d952bb1..2cdb63862 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ServerSessionStub.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ServerSessionStub.java @@ -19,13 +19,21 @@ import cz.cvut.kbss.jopa.environment.utils.TestEnvironmentUtils; import cz.cvut.kbss.jopa.model.AbstractEntityManager; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.sessions.cache.DisabledCacheManager; import cz.cvut.kbss.jopa.transactions.EntityTransaction; +import static org.mockito.Mockito.spy; + public class ServerSessionStub extends ServerSession { private final ConnectionWrapper connection; - public ServerSessionStub(ConnectionWrapper conn) { + private final CacheManager disabledCache = spy(new DisabledCacheManager()); + + public ServerSessionStub(MetamodelImpl metamodel, ConnectionWrapper conn) { + super(metamodel); this.connection = conn; } @@ -42,4 +50,9 @@ public boolean isEntityType(Class cls) { public void transactionStarted(EntityTransaction t, AbstractEntityManager em) { // Do nothing } + + @Override + public CacheManager getLiveObjectCache() { + return disabledCache; + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkGetReferenceTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkGetReferenceTest.java index 5f0569b70..b02a4b8bf 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkGetReferenceTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkGetReferenceTest.java @@ -22,23 +22,50 @@ import cz.cvut.kbss.jopa.environment.OWLClassL; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException; -import cz.cvut.kbss.jopa.model.EntityManagerImpl; +import cz.cvut.kbss.jopa.model.EntityState; import cz.cvut.kbss.jopa.model.LoadState; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; +import cz.cvut.kbss.jopa.utils.Configuration; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@Disabled public class UnitOfWorkGetReferenceTest extends UnitOfWorkTestBase { + // TODO Support ChangeTrackingUoW and OnCommitChangePropagatingUoW + @BeforeEach protected void setUp() throws Exception { super.setUp(); } + @Override + protected AbstractUnitOfWork initUnitOfWork() { + return new ChangeTrackingUnitOfWork(serverSessionStub, new Configuration()); + } + @Test void getReferenceReturnsExistingCloneWhenItIsAlreadyManaged() { final OWLClassA existing = (OWLClassA) uow.registerExistingObject(entityA, descriptor); @@ -74,7 +101,8 @@ void getReferenceLoadsReferenceFromStorageWhenItIsNotManaged() { final OWLClassA reference = new OWLClassA(entityA.getUri()); when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); - assertEquals(reference, result); + assertNotNull(result); + assertEquals(reference.getUri(), result.getUri()); verify(storageMock).getReference(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor)); } @@ -91,8 +119,8 @@ void getStateReturnsManagedForInstanceRetrieveUsingGetReference() { final OWLClassA reference = new OWLClassA(entityA.getUri()); when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); - assertEquals(EntityManagerImpl.State.MANAGED, uow.getState(result)); - assertEquals(EntityManagerImpl.State.MANAGED, uow.getState(result, descriptor)); + assertEquals(EntityState.MANAGED, uow.getState(result)); + assertEquals(EntityState.MANAGED, uow.getState(result, descriptor)); } @Test @@ -103,7 +131,7 @@ void removeOfInstanceRetrievedUsingGetReferenceSchedulesItForDeletion() { assertTrue(uow.contains(result)); uow.removeObject(result); assertFalse(uow.contains(result)); - assertEquals(EntityManagerImpl.State.REMOVED, uow.getState(result)); + assertEquals(EntityState.REMOVED, uow.getState(result)); verify(storageMock).remove(entityA.getUri(), OWLClassA.class, descriptor); } @@ -112,7 +140,7 @@ void loadEntityFieldLoadsValueOfAttributeOfInstanceRetrievedUsingGetReference() final OWLClassL reference = new OWLClassL(entityL.getUri()); when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); final OWLClassL result = uow.getReference(OWLClassL.class, entityL.getUri(), descriptor); - uow.loadEntityField(result, OWLClassL.getSetField()); + uow.loadEntityField(result, metamodelMocks.forOwlClassL().setAttribute()); verify(storageMock).loadFieldValue(result, metamodelMocks.forOwlClassL().setAttribute(), descriptor); assertEquals(LoadState.LOADED, uow.isLoaded(result, OWLClassL.getSetField().getName())); } @@ -123,9 +151,9 @@ void loadEntityFieldDoesNothingWhenLazilyLoadedAttributeOfInstanceRetrievedUsing final OWLClassL reference = new OWLClassL(entityL.getUri()); when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); final OWLClassL result = uow.getReference(OWLClassL.class, entityL.getUri(), descriptor); - uow.loadEntityField(result, OWLClassL.getSetField()); + uow.loadEntityField(result, metamodelMocks.forOwlClassL().setAttribute()); // Call it twice. Storage should be called only once - uow.loadEntityField(result, OWLClassL.getSetField()); + uow.loadEntityField(result, metamodelMocks.forOwlClassL().setAttribute()); verify(storageMock).loadFieldValue(result, metamodelMocks.forOwlClassL().setAttribute(), descriptor); assertEquals(LoadState.LOADED, uow.isLoaded(result, OWLClassL.getSetField().getName())); } @@ -147,7 +175,7 @@ void attributeChangedDoesNotRegisterChangeForInstanceRetrievedUsingGetReference( when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); uow.attributeChanged(result, OWLClassA.getStrAttField()); - assertFalse(uow.getUowChangeSet().hasChanges()); + assertFalse(uow.uowChangeSet.hasChanges()); } @Test @@ -170,13 +198,13 @@ void removeCreatesRemoveChangeOnCommitForInstanceRetrievedUsingGetReference() { final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); uow.removeObject(result); uow.commit(); - verify(cacheManagerMock).evict(OWLClassA.class, reference.getUri(), descriptor.getSingleContext().orElse(null)); + verify(serverSessionStub.getLiveObjectCache()).evict(OWLClassA.class, reference.getUri(), descriptor.getSingleContext().orElse(null)); } @Test void getReferenceLoadsOriginalFromSecondLevelCacheWhenPresent() { - when(cacheManagerMock.contains(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(true); - when(cacheManagerMock.get(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(entityA); + when(serverSessionStub.getLiveObjectCache().contains(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(true); + when(serverSessionStub.getLiveObjectCache().get(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(entityA); final OWLClassA reference = new OWLClassA(entityA.getUri()); when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); @@ -186,8 +214,8 @@ void getReferenceLoadsOriginalFromSecondLevelCacheWhenPresent() { @Test void changesToGetReferenceResultAreMergedIntoOriginalInCache() { when(transactionMock.isActive()).thenReturn(true); - when(cacheManagerMock.contains(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(true); - when(cacheManagerMock.get(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(entityA); + when(serverSessionStub.getLiveObjectCache().contains(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(true); + when(serverSessionStub.getLiveObjectCache().get(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(entityA); final OWLClassA reference = new OWLClassA(entityA.getUri()); when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); final OWLClassA a = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); @@ -200,14 +228,14 @@ void changesToGetReferenceResultAreMergedIntoOriginalInCache() { @Test void uowCommitEvictsInstanceRetrievedUsingGetReferenceFromCacheWhenItWasNotPresentThereOnRetrieval() { when(transactionMock.isActive()).thenReturn(true); - when(cacheManagerMock.contains(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(false); + when(serverSessionStub.getLiveObjectCache().contains(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(false); final OWLClassA reference = new OWLClassA(entityA.getUri()); when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); final OWLClassA a = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); final String strValue = "string value"; a.setStringAttribute(strValue); uow.commit(); - verify(cacheManagerMock).evict(OWLClassA.class, entityA.getUri(), descriptor.getSingleContext().orElse(null)); + verify(serverSessionStub.getLiveObjectCache()).evict(OWLClassA.class, entityA.getUri(), descriptor.getSingleContext().orElse(null)); } @Test @@ -223,7 +251,7 @@ void attributeChangeSetsChangeRecordToPreventCachingWhenNewValueWasRetrievedUsin uow.attributeChanged(changed, OWLClassD.getOwlClassAField()); - final ObjectChangeSet changeSet = uow.getUowChangeSet().getExistingObjectChanges(owner); + final ObjectChangeSet changeSet = uow.uowChangeSet.getExistingObjectChanges(owner); assertFalse(changeSet.getChanges().isEmpty()); final Optional changeRecord = changeSet.getChanges().stream().filter(chr -> chr.getNewValue().equals(ref)).findFirst(); @@ -245,7 +273,7 @@ void mergeDetachedMarksChangeRecordForAttributeWithGetReferenceResultAsPreventin uow.mergeDetached(toMerge, descriptor); - final ObjectChangeSet changeSet = uow.getUowChangeSet().getExistingObjectChanges(owner); + final ObjectChangeSet changeSet = uow.uowChangeSet.getExistingObjectChanges(owner); assertFalse(changeSet.getChanges().isEmpty()); final Optional changeRecord = changeSet.getChanges().stream().filter(chr -> chr.getNewValue().equals(ref)).findFirst(); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkMergeTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkMergeTestRunner.java similarity index 79% rename from jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkMergeTest.java rename to jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkMergeTestRunner.java index 571829de7..2ea890a29 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkMergeTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkMergeTestRunner.java @@ -17,38 +17,48 @@ */ package cz.cvut.kbss.jopa.sessions; -import cz.cvut.kbss.jopa.environment.*; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassC; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.OWLClassF; +import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.exceptions.InferredAttributeModifiedException; import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; -import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; +import cz.cvut.kbss.jopa.sessions.change.UnitOfWorkChangeSet; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.ontodriver.model.Assertion; 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.mockito.ArgumentCaptor; import java.net.URI; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -public class UnitOfWorkMergeTest extends UnitOfWorkTestBase { +abstract class UnitOfWorkMergeTestRunner extends UnitOfWorkTestBase { - @BeforeEach - public void setUp() throws Exception { - super.setUp(); - } - - @Test - void testMergeDetachedExisting() { - mergeDetachedTest(); - } - - private void mergeDetachedTest() { + OWLClassA mergeDetached() { when(storageMock.contains(entityA.getUri(), entityA.getClass(), descriptor)).thenReturn(Boolean.TRUE); final OWLClassA orig = new OWLClassA(); orig.setUri(entityA.getUri()); @@ -58,32 +68,27 @@ private void mergeDetachedTest() { it.next(); it.remove(); when(storageMock.find(any())).thenReturn(orig); + defaultLoadStateDescriptor(orig); - final OWLClassA res = uow.mergeDetached(entityA, descriptor); - assertNotNull(res); - assertEquals(entityA.getUri(), res.getUri()); - final ArgumentCaptor ac = ArgumentCaptor.forClass(FieldSpecification.class); - verify(storageMock, atLeastOnce()).merge(any(Object.class), ac.capture(), eq(descriptor)); - final List mergedFields = ac.getAllValues(); - assertTrue(mergedFields.contains(metamodelMocks.forOwlClassA().stringAttribute())); - assertTrue(mergedFields.contains(metamodelMocks.forOwlClassA().typesSpec())); + return uow.mergeDetached(entityA, descriptor); } @Test void mergeDetachedEvictsInstanceFromCache() { - when(cacheManagerMock.contains(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(Boolean.TRUE); - mergeDetachedTest(); - verify(cacheManagerMock).evict(OWLClassA.class, entityA.getUri(), CONTEXT_URI); + when(serverSessionStub.getLiveObjectCache().contains(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(true); + mergeDetached(); + verify(serverSessionStub.getLiveObjectCache()).evict(OWLClassA.class, entityA.getUri(), CONTEXT_URI); } @Test void mergeDetachedRegistersNewObjectWhenItDoesNotExist() { - when(storageMock.contains(entityA.getUri(), entityA.getClass(), descriptor)) - .thenReturn(Boolean.FALSE); + when(storageMock.contains(entityA.getUri(), entityA.getClass(), descriptor)).thenReturn(false); + assertFalse(uow.contains(entityA)); + defaultLoadStateDescriptor(entityA); final OWLClassA res = uow.mergeDetached(entityA, descriptor); assertNotNull(res); assertSame(entityA, res); - verify(storageMock).persist(entityA.getUri(), entityA, descriptor); + assertTrue(uow.isObjectNew(res)); } @Test @@ -95,10 +100,11 @@ void mergeRegistersChangesInUoWChangeSet() throws Exception { clone.setTypes(Collections.emptySet()); when(storageMock.contains(entityA.getUri(), OWLClassA.class, descriptor)).thenReturn(true); when(storageMock.find(any())).thenReturn(entityA); + defaultLoadStateDescriptor(entityA); uow.mergeDetached(clone, descriptor); assertTrue(uow.hasChanges()); - final UnitOfWorkChangeSet changeSet = uow.getUowChangeSet(); + final UnitOfWorkChangeSet changeSet = uow.uowChangeSet; final ObjectChangeSet objectChanges = changeSet.getExistingObjectChanges(entityA); assertNotNull(objectChanges); assertEquals(2, objectChanges.getChanges().size()); @@ -129,6 +135,7 @@ void mergeReturnsInstanceWithReferencesWithOriginalValues() { final LoadingParameters dParams = new LoadingParameters<>(OWLClassD.class, dOriginal.getUri(), descriptor, true); when(storageMock.find(dParams)).thenReturn(dOriginal); + defaultLoadStateDescriptor(dOriginal, aOriginal); final OWLClassD result = uow.mergeDetached(entityD, descriptor); assertEquals(aOriginal.getStringAttribute(), result.getOwlClassA().getStringAttribute()); @@ -144,6 +151,7 @@ void mergeReturnsInstanceWithUpdatedReferenceWhenItWasChangedInTheDetachedObject final LoadingParameters dParams = new LoadingParameters<>(OWLClassD.class, dOriginal.getUri(), descriptor, true); when(storageMock.find(dParams)).thenReturn(dOriginal); + defaultLoadStateDescriptor(dOriginal, aOriginal); final OWLClassD result = uow.mergeDetached(entityD, descriptor); assertEquals(entityA.getUri(), result.getOwlClassA().getUri()); @@ -153,6 +161,7 @@ void mergeReturnsInstanceWithUpdatedReferenceWhenItWasChangedInTheDetachedObject @Test void mergeMergesChangesIntoExistingManagedInstanceAndReturnsIt() { + defaultLoadStateDescriptor(entityA); final OWLClassA managed = (OWLClassA) uow.registerExistingObject(entityA, descriptor); final OWLClassA detached = new OWLClassA(managed.getUri()); detached.setTypes(new HashSet<>(managed.getTypes())); @@ -166,44 +175,35 @@ void mergeMergesChangesIntoExistingManagedInstanceAndReturnsIt() { } @Test - void mergeDoesNotAddChangeSetToUoWChangeSetWhenItContainsNoChanges() throws Exception { + void mergeDoesNotAddChangeSetToUoWChangeSetWhenItContainsNoChanges() { + defaultLoadStateDescriptor(entityA, entityD); final OWLClassD managed = (OWLClassD) uow.registerExistingObject(entityD, descriptor); - final OWLClassA a2 = new OWLClassA(Generators.createIndividualIdentifier()); - a2.setStringAttribute("a2"); - final OWLClassA a2Clone = (OWLClassA) uow.registerExistingObject(a2, descriptor); - managed.setOwlClassA(a2Clone); when(transactionMock.isActive()).thenReturn(true); when(storageMock.contains(entityD.getUri(), OWLClassD.class, descriptor)).thenReturn(true); - uow.attributeChanged(managed, OWLClassD.getOwlClassAField()); - assertTrue(uow.getUowChangeSet().hasChanges()); - final ObjectChangeSet originalChangeSet = uow.getUowChangeSet().getExistingObjectChanges(entityD); - assertTrue(originalChangeSet.hasChanges()); final OWLClassD detached = new OWLClassD(managed.getUri()); - detached.setOwlClassA(a2Clone); + detached.setOwlClassA(entityA); uow.mergeDetached(detached, descriptor); - assertTrue(uow.getUowChangeSet().hasChanges()); - final ObjectChangeSet result = uow.getUowChangeSet().getExistingObjectChanges(entityD); - assertEquals(originalChangeSet, result); - assertTrue(result.hasChanges()); + assertNull(uow.uowChangeSet.getExistingObjectChanges(entityD)); } @Test void mergeDetachedEvictsClassesPossiblyReferencingMergedTypeFromCache() { when(metamodelMock.getReferringTypes(OWLClassA.class)).thenReturn( new HashSet<>(Arrays.asList(OWLClassD.class, OWLClassC.class))); + defaultLoadStateDescriptor(entityA); final OWLClassA managed = (OWLClassA) uow.registerExistingObject(entityA, descriptor); final OWLClassA detached = new OWLClassA(managed.getUri()); detached.setTypes(new HashSet<>(managed.getTypes())); final String detachedString = "detachedStringAttribute"; detached.setStringAttribute(detachedString); when(storageMock.contains(managed.getUri(), OWLClassA.class, descriptor)).thenReturn(true); - when(cacheManagerMock.contains(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(true); + when(serverSessionStub.getLiveObjectCache().contains(OWLClassA.class, entityA.getUri(), descriptor)).thenReturn(true); uow.mergeDetached(detached, descriptor); - verify(cacheManagerMock).evict(OWLClassA.class, entityA.getUri(), descriptor.getSingleContext().orElse(null)); - verify(cacheManagerMock).evict(OWLClassD.class); - verify(cacheManagerMock).evict(OWLClassC.class); + verify(serverSessionStub.getLiveObjectCache()).evict(OWLClassA.class, entityA.getUri(), descriptor.getSingleContext().orElse(null)); + verify(serverSessionStub.getLiveObjectCache()).evict(OWLClassD.class); + verify(serverSessionStub.getLiveObjectCache()).evict(OWLClassC.class); } @Test @@ -215,6 +215,7 @@ void mergeDetachedThrowsInferredAttributeModifiedExceptionOnChangeToInferredAttr when(transactionMock.isActive()).thenReturn(true); when(storageMock.contains(detached.getUri(), OWLClassF.class, descriptor)).thenReturn(true); when(storageMock.find(any(LoadingParameters.class))).thenReturn(original); + defaultLoadStateDescriptor(original); final Assertion assertion = Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_f_stringAttribute), true); doAnswer(inv -> { @@ -241,6 +242,7 @@ void mergeDetachedIgnoresRemovalsOfInferredAttributeValuesWhenConfiguredTo() { when(transactionMock.isActive()).thenReturn(true); when(storageMock.contains(detached.getUri(), OWLClassF.class, descriptor)).thenReturn(true); when(storageMock.find(any(LoadingParameters.class))).thenReturn(original); + defaultLoadStateDescriptor(original); final Assertion assertion = Assertion.createDataPropertyAssertion(URI.create(Vocabulary.p_f_stringAttribute), true); doAnswer(inv -> { diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkTestBase.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkTestBase.java index 144915940..914a64f30 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkTestBase.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkTestBase.java @@ -23,20 +23,19 @@ import cz.cvut.kbss.jopa.environment.OWLClassL; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; -import cz.cvut.kbss.jopa.model.EntityManagerImpl; import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; import cz.cvut.kbss.jopa.transactions.EntityTransaction; -import cz.cvut.kbss.jopa.utils.Configuration; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import java.lang.reflect.Field; import java.net.URI; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; public abstract class UnitOfWorkTestBase { @@ -52,15 +51,9 @@ public abstract class UnitOfWorkTestBase { @Mock protected MetamodelImpl metamodelMock; - @Mock - CacheManager cacheManagerMock; - @Mock ConnectionWrapper storageMock; - @Mock - protected EntityManagerImpl emMock; - @Mock protected EntityTransaction transactionMock; @@ -70,28 +63,24 @@ public abstract class UnitOfWorkTestBase { CloneBuilder cloneBuilder; - protected UnitOfWorkImpl uow; + protected AbstractUnitOfWork uow; protected void setUp() throws Exception { - MockitoAnnotations.openMocks(this); this.descriptor = new EntityDescriptor(CONTEXT_URI); - this.serverSessionStub = spy(new ServerSessionStub(storageMock)); - when(serverSessionStub.getMetamodel()).thenReturn(metamodelMock); - when(serverSessionStub.getLiveObjectCache()).thenReturn(cacheManagerMock); - when(serverSessionStub.acquireConnection()).thenReturn(storageMock); - when(emMock.getTransaction()).thenReturn(transactionMock); - when(emMock.getConfiguration()).thenReturn(new Configuration()); + this.serverSessionStub = spy(new ServerSessionStub(metamodelMock, storageMock)); this.metamodelMocks = new MetamodelMocks(); metamodelMocks.setMocks(metamodelMock); - uow = new UnitOfWorkImpl(serverSessionStub); - uow.setEntityManager(emMock); - final Field cbField = UnitOfWorkImpl.class.getDeclaredField("cloneBuilder"); + this.uow = initUnitOfWork(); + uow.begin(); + final Field cbField = AbstractUnitOfWork.class.getDeclaredField("cloneBuilder"); cbField.setAccessible(true); this.cloneBuilder = spy((CloneBuilder) cbField.get(uow)); cbField.set(uow, cloneBuilder); initEntities(); } + protected abstract AbstractUnitOfWork initUnitOfWork(); + private void initEntities() { this.entityA = Generators.generateOwlClassAInstance(); this.entityB = new OWLClassB(Generators.createIndividualIdentifier()); @@ -100,4 +89,11 @@ private void initEntities() { this.entityL = new OWLClassL(); entityL.setUri(Generators.createIndividualIdentifier()); } + + protected void defaultLoadStateDescriptor(Object... entities) { + for (Object e : entities) { + final LoadStateDescriptor desc = LoadStateDescriptorFactory.createAllLoaded(e, (EntityType) metamodelMock.entity(e.getClass())); + uow.getLoadStateRegistry().put(e, desc); + } + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/AbstractCacheManagerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/AbstractCacheManagerTest.java index 2a3757c75..cade07887 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/AbstractCacheManagerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/AbstractCacheManagerTest.java @@ -22,10 +22,12 @@ import cz.cvut.kbss.jopa.environment.OWLClassD; import cz.cvut.kbss.jopa.environment.OWLClassM; import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.model.LoadState; 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.sessions.CacheManager; +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import org.junit.jupiter.api.Test; import java.net.URI; @@ -35,7 +37,13 @@ import java.util.Map.Entry; import java.util.Set; -import static org.junit.jupiter.api.Assertions.*; +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -75,17 +83,21 @@ Descriptor descriptor(URI context) { @Test public void testAddIntoContext() { final Descriptor descriptor = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptor); + manager.add(testA.getUri(), testA, descriptors(descriptor)); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptor)); final Object res = manager.get(testA.getClass(), testA.getUri(), descriptor); assertNotNull(res); assertSame(testA, res); } + protected Descriptors descriptors(Descriptor repoDescriptor) { + return new Descriptors(repoDescriptor, new LoadStateDescriptor<>(testA, mock(EntityType.class), LoadState.UNKNOWN)); + } + @Test public void testAddToDefault() { final Descriptor descriptor = descriptor(null); - manager.add(testA.getUri(), testA, descriptor); + manager.add(testA.getUri(), testA, descriptors(descriptor)); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptor)); assertFalse(manager.contains(testA.getClass(), testA.getUri(), descriptor(CONTEXT_ONE))); assertFalse(manager.contains(testA.getClass(), testA.getUri(), descriptor(CONTEXT_TWO))); @@ -97,18 +109,18 @@ public void testAddToDefault() { @Test public void addNullThrowsNullPointerException() { assertThrows(NullPointerException.class, - () -> manager.add(URI.create("http://blahblahblah"), null, descriptor(null))); + () -> manager.add(URI.create("http://blahblahblah"), null, descriptors(descriptor(null)))); } @Test public void addingWithDuplicateIdentifierReplacesExistingRecord() { final Descriptor descriptor = descriptor(CONTEXT_ONE); - manager.add(testA.getUri(), testA, descriptor); + manager.add(testA.getUri(), testA, descriptors(descriptor)); final OWLClassA duplicate = new OWLClassA(); final String newStr = testA.getStringAttribute() + "duplicated"; duplicate.setStringAttribute(newStr); duplicate.setUri(testA.getUri()); - manager.add(duplicate.getUri(), duplicate, descriptor); + manager.add(duplicate.getUri(), duplicate, descriptors(descriptor)); final OWLClassA res = manager.get(testA.getClass(), testA.getUri(), descriptor); assertNotNull(res); assertEquals(newStr, res.getStringAttribute()); @@ -118,12 +130,12 @@ public void addingWithDuplicateIdentifierReplacesExistingRecord() { public void addWithSameIdToDifferentContextsRetainsBothRecords() { final Descriptor dOne = descriptor(CONTEXT_ONE); final Descriptor dTwo = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, dOne); + manager.add(testA.getUri(), testA, descriptors(dOne)); final OWLClassA duplicate = new OWLClassA(); duplicate.setUri(testA.getUri()); final String newStr = testA.getStringAttribute() + "duplicated"; duplicate.setStringAttribute(newStr); - manager.add(duplicate.getUri(), duplicate, dTwo); + manager.add(duplicate.getUri(), duplicate, descriptors(dTwo)); assertTrue(manager.contains(testA.getClass(), testA.getUri(), dOne)); assertTrue(manager.contains(duplicate.getClass(), duplicate.getUri(), dTwo)); assertSame(testA, manager.get(testA.getClass(), testA.getUri(), dOne)); @@ -133,14 +145,14 @@ public void addWithSameIdToDifferentContextsRetainsBothRecords() { @Test public void testContainsDefault() { final Descriptor descriptor = descriptor(null); - manager.add(testA.getUri(), testA, descriptor); + manager.add(testA.getUri(), testA, descriptors(descriptor)); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptor)); assertFalse(manager.contains(testB.getClass(), testA.getUri(), descriptor(CONTEXT_TWO))); } @Test public void testContainsWithContext() { - manager.add(testA.getUri(), testA, descriptor(CONTEXT_TWO)); + manager.add(testA.getUri(), testA, descriptors(descriptor(CONTEXT_TWO))); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptor(CONTEXT_TWO))); assertFalse(manager.contains(testA.getClass(), testA.getUri(), descriptor(CONTEXT_ONE))); } @@ -157,7 +169,7 @@ public void containsWithNullArgumentsReturnsFalse() { @Test public void testGetObject() { final Descriptor descriptor = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptor); + manager.add(testA.getUri(), testA, descriptors(descriptor)); final Object res = manager.get(testA.getClass(), testA.getUri(), descriptor); assertEquals(testA, res); } @@ -165,7 +177,7 @@ public void testGetObject() { @Test public void getWithWrongContextReturnsNull() { final Descriptor descriptor = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptor); + manager.add(testA.getUri(), testA, descriptors(descriptor)); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptor)); final OWLClassA res = manager.get(testA.getClass(), testA.getUri(), descriptor(CONTEXT_ONE)); assertNull(res); @@ -174,7 +186,7 @@ public void getWithWrongContextReturnsNull() { @Test public void getWithNullIdReturnsNull() { final Descriptor descriptor = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptor); + manager.add(testA.getUri(), testA, descriptors(descriptor)); final Object o = manager.get(OWLClassA.class, null, descriptor); assertNull(o); } @@ -182,8 +194,8 @@ public void getWithNullIdReturnsNull() { @Test public void getByNonMatchingClassReturnsNull() { final Descriptor descriptor = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptor); - manager.add(testB.getUri(), testB, descriptor); + manager.add(testA.getUri(), testA, descriptors(descriptor)); + manager.add(testB.getUri(), testB, descriptors(descriptor)); final Object o = manager.get(OWLClassD.class, testA.getUri(), descriptor); assertNull(o); } @@ -191,8 +203,8 @@ public void getByNonMatchingClassReturnsNull() { @Test public void getByUnknownIdReturnsNull() { final Descriptor descriptor = descriptor(CONTEXT_ONE); - manager.add(testA.getUri(), testA, descriptor); - manager.add(testB.getUri(), testB, descriptor); + manager.add(testA.getUri(), testA, descriptors(descriptor)); + manager.add(testB.getUri(), testB, descriptors(descriptor)); final URI unknownId = URI.create("http://unknownId"); final Object o = manager.get(OWLClassA.class, unknownId, descriptor); assertNull(o); @@ -201,7 +213,7 @@ public void getByUnknownIdReturnsNull() { @Test public void evictAllRemovesAllRecords() { final Descriptor descriptor = descriptor(CONTEXT_ONE); - manager.add(testA.getUri(), testA, descriptor); + manager.add(testA.getUri(), testA, descriptors(descriptor)); addAllToCache(listOfBs, manager); manager.evictAll(); assertFalse(manager.contains(testA.getClass(), testA.getUri(), descriptor)); @@ -213,8 +225,8 @@ public void evictAllRemovesAllRecords() { Class evictByClass() { final Descriptor descriptorOne = descriptor(CONTEXT_ONE); final Descriptor descriptorTwo = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptorOne); - manager.add(testB.getUri(), testB, descriptorTwo); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); + manager.add(testB.getUri(), testB, descriptors(descriptorTwo)); addAllToCache(listOfBs, manager); manager.evict(OWLClassB.class); assertTrue(manager.contains(OWLClassA.class, testA.getUri(), descriptorOne)); @@ -227,7 +239,7 @@ Class evictByClass() { @Test public void evictByClassThrowsNPXForNullArgument() { - manager.add(testA.getUri(), testA, descriptor(CONTEXT_ONE)); + manager.add(testA.getUri(), testA, descriptors(descriptor(CONTEXT_ONE))); addAllToCache(listOfBs, manager); assertThrows(NullPointerException.class, () -> manager.evict((Class) null)); } @@ -235,8 +247,8 @@ public void evictByClassThrowsNPXForNullArgument() { URI evictByContext() { final Descriptor descriptorOne = descriptor(CONTEXT_ONE); final Descriptor descriptorTwo = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptorOne); - manager.add(testB.getUri(), testB, descriptorTwo); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); + manager.add(testB.getUri(), testB, descriptors(descriptorTwo)); manager.evict(CONTEXT_ONE); assertFalse(manager.contains(testA.getClass(), testA.getUri(), descriptorOne)); assertTrue(manager.contains(testB.getClass(), testB.getUri(), descriptorTwo)); @@ -245,7 +257,7 @@ URI evictByContext() { @Test public void evictByContextWithNullArgumentEvictsDefaultContext() { - manager.add(testA.getUri(), testA, descriptor(null)); + manager.add(testA.getUri(), testA, descriptors(descriptor(null))); manager.evict((URI) null); assertFalse(manager.contains(testA.getClass(), testA.getUri(), descriptor(null))); assertNull(manager.get(testA.getClass(), testA.getUri(), descriptor(null))); @@ -255,8 +267,8 @@ public void evictByContextWithNullArgumentEvictsDefaultContext() { public void evictByContextRetainsRecordsWhenUnknownContextIsUsed() { final Descriptor descriptorOne = descriptor(CONTEXT_ONE); final Descriptor descriptorTwo = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptorOne); - manager.add(testB.getUri(), testB, descriptorTwo); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); + manager.add(testB.getUri(), testB, descriptors(descriptorTwo)); manager.evict(URI.create("http://someUnknownContextUri")); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptorOne)); assertTrue(manager.contains(testB.getClass(), testB.getUri(), descriptorTwo)); @@ -266,8 +278,8 @@ public void evictByContextRetainsRecordsWhenUnknownContextIsUsed() { public void evictInferredClassesRemovesInstancesOfInferredClasses() { final Descriptor descriptorOne = descriptor(CONTEXT_ONE); final Descriptor descriptorTwo = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptorOne); - manager.add(testB.getUri(), testB, descriptorTwo); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); + manager.add(testB.getUri(), testB, descriptors(descriptorTwo)); final Set> inferred = Collections.singleton(testA.getClass()); manager.setInferredClasses(inferred); manager.evictInferredObjects(); @@ -279,11 +291,11 @@ public void evictInferredClassesRemovesInstancesOfInferredClasses() { public void testEvictByContextClassAndPrimaryKey() throws Exception { final Descriptor descriptorOne = descriptor(CONTEXT_ONE); final Descriptor descriptorTwo = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptorTwo); + manager.add(testA.getUri(), testA, descriptors(descriptorTwo)); final OWLClassA duplicate = new OWLClassA(); duplicate.setUri(testA.getUri()); duplicate.setStringAttribute("Duplicate entity."); - manager.add(duplicate.getUri(), duplicate, descriptorOne); + manager.add(duplicate.getUri(), duplicate, descriptors(descriptorOne)); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptorTwo)); assertTrue(manager.contains(duplicate.getClass(), duplicate.getUri(), descriptorOne)); @@ -294,28 +306,28 @@ public void testEvictByContextClassAndPrimaryKey() throws Exception { @Test public void evictByNullIdentifierThrowsNPX() { - manager.add(testA.getUri(), testA, descriptor(CONTEXT_ONE)); + manager.add(testA.getUri(), testA, descriptors(descriptor(CONTEXT_ONE))); assertThrows(NullPointerException.class, () -> manager.evict(null, null, CONTEXT_ONE)); } private void addAllToCache(Map entities, CacheManager manager) { final Descriptor descriptor = descriptor(CONTEXT_ONE); for (Entry e : entities.entrySet()) { - manager.add(e.getKey(), e.getValue(), descriptor); + manager.add(e.getKey(), e.getValue(), descriptors(descriptor)); } } @Test public void cacheAddWithStringIdentifier() { final Descriptor descriptor = descriptor(CONTEXT_ONE); - manager.add(testM.getKey(), testM, descriptor); + manager.add(testM.getKey(), testM, descriptors(descriptor)); assertTrue(manager.contains(OWLClassM.class, testM.getKey(), descriptor)); } @Test public void cacheEvictWithStringIdentifier() { final Descriptor descriptor = descriptor(null); - manager.add(testM.getKey(), testM, descriptor); + manager.add(testM.getKey(), testM, descriptors(descriptor)); assertTrue(manager.contains(OWLClassM.class, testM.getKey(), descriptor)); manager.evict(OWLClassM.class, testM.getKey(), null); assertFalse(manager.contains(OWLClassM.class, testM.getKey(), descriptor)); @@ -324,7 +336,7 @@ public void cacheEvictWithStringIdentifier() { @Test public void containsReturnsFalseWhenDescriptorsDoNotMatch() { final Descriptor descriptorOne = new EntityDescriptor(); - manager.add(testA.getUri(), testA, descriptorOne); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); final Descriptor descriptorTwo = new EntityDescriptor(); descriptorTwo.setLanguage("cs"); assertTrue(manager.contains(OWLClassA.class, testA.getUri(), descriptorOne)); @@ -335,7 +347,7 @@ public void containsReturnsFalseWhenDescriptorsDoNotMatch() { public void getReturnsNullWhenDescriptorsDoNotMatch() throws Exception { final Descriptor descriptorOne = new EntityDescriptor(); descriptorOne.setLanguage("en"); - manager.add(testA.getUri(), testA, descriptorOne); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); final Descriptor descriptorTwo = new EntityDescriptor(); descriptorTwo.setLanguage("en"); final Attribute att = mock(Attribute.class); @@ -351,10 +363,10 @@ public void addReplacesInstanceTogetherWithDescriptor() { descriptorOne.setLanguage("en"); final Descriptor descriptorTwo = new EntityDescriptor(); descriptorTwo.setLanguage("cs"); - manager.add(testA.getUri(), testA, descriptorOne); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); assertTrue(manager.contains(OWLClassA.class, testA.getUri(), descriptorOne)); assertFalse(manager.contains(OWLClassA.class, testA.getUri(), descriptorTwo)); - manager.add(testA.getUri(), testA, descriptorTwo); + manager.add(testA.getUri(), testA, descriptors(descriptorTwo)); assertFalse(manager.contains(OWLClassA.class, testA.getUri(), descriptorOne)); assertTrue(manager.contains(OWLClassA.class, testA.getUri(), descriptorTwo)); } @@ -365,11 +377,11 @@ public void addRemovesOldDescriptorWhenInstanceIsReplacedByAnother() throws Exce descriptorOne.setLanguage("en"); final Descriptor descriptorTwo = new EntityDescriptor(); descriptorTwo.setLanguage("cs"); - manager.add(testA.getUri(), testA, descriptorOne); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); assertTrue(manager.contains(OWLClassA.class, testA.getUri(), descriptorOne)); assertFalse(manager.contains(OWLClassA.class, testA.getUri(), descriptorTwo)); final OWLClassA newA = new OWLClassA(testA.getUri()); - manager.add(newA.getUri(), newA, descriptorTwo); + manager.add(newA.getUri(), newA, descriptors(descriptorTwo)); assertFalse(manager.contains(OWLClassA.class, testA.getUri(), descriptorOne)); assertTrue(manager.contains(OWLClassA.class, newA.getUri(), descriptorTwo)); final Map descriptors = extractDescriptors(); @@ -383,7 +395,7 @@ public void addRemovesOldDescriptorWhenInstanceIsReplacedByAnother() throws Exce public void evictRemovesInstanceDescriptor() throws Exception { final Descriptor descriptorOne = new EntityDescriptor(); descriptorOne.setLanguage("en"); - manager.add(testA.getUri(), testA, descriptorOne); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); assertTrue(extractDescriptors().containsKey(testA)); manager.evict(OWLClassA.class, testA.getUri(), null); assertFalse(extractDescriptors().containsKey(testA)); @@ -393,9 +405,9 @@ public void evictRemovesInstanceDescriptor() throws Exception { public void evictByClassRemovesInstanceDescriptors() throws Exception { final Descriptor descriptorOne = new EntityDescriptor(); descriptorOne.setLanguage("en"); - manager.add(testA.getUri(), testA, descriptor(CONTEXT_ONE)); + manager.add(testA.getUri(), testA, descriptors(descriptor(CONTEXT_ONE))); final OWLClassA anotherA = new OWLClassA(Generators.createIndividualIdentifier()); - manager.add(anotherA.getUri(), anotherA, descriptor(null)); + manager.add(anotherA.getUri(), anotherA, descriptors(descriptor(null))); assertTrue(extractDescriptors().containsKey(testA)); assertTrue(extractDescriptors().containsKey(anotherA)); manager.evict(OWLClassA.class); @@ -406,10 +418,10 @@ public void evictByClassRemovesInstanceDescriptors() throws Exception { @Test public void evictByContextRemovesInstanceDescriptors() throws Exception { final Descriptor descriptorOne = new EntityDescriptor(CONTEXT_ONE); - manager.add(testA.getUri(), testA, descriptorOne); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); final Descriptor descriptorTwo = new EntityDescriptor(); descriptorTwo.setLanguage("en"); - manager.add(testB.getUri(), testB, descriptorTwo); + manager.add(testB.getUri(), testB, descriptors(descriptorTwo)); assertTrue(manager.contains(OWLClassA.class, testA.getUri(), descriptorOne)); assertTrue(manager.contains(OWLClassB.class, testB.getUri(), descriptorTwo)); manager.evict(CONTEXT_ONE); @@ -423,9 +435,17 @@ public void evictByContextRemovesInstanceDescriptors() throws Exception { public void cacheSupportsEntitiesOverridingEqualsAndHashCode() { final URI uri = Generators.createIndividualIdentifier(); final Descriptor descriptorOne = new EntityDescriptor(CONTEXT_ONE); - manager.add(uri, URI.create(uri.toString()), descriptorOne); - manager.add(uri, URI.create(uri.toString()), new EntityDescriptor()); + manager.add(uri, URI.create(uri.toString()), descriptors(descriptorOne)); + manager.add(uri, URI.create(uri.toString()), descriptors(new EntityDescriptor())); manager.evict(URI.class, uri, CONTEXT_ONE); assertTrue(manager.contains(URI.class, uri, new EntityDescriptor())); } + + @Test + void getLoadStateDescriptorReturnsLoadStateDescriptorAssociatedWithSpecifiedCacheEntry() { + final Descriptor descriptor = descriptor(CONTEXT_ONE); + final Descriptors entryDescriptors = descriptors(descriptor); + manager.add(testM.getKey(), testM, entryDescriptors); + assertEquals(entryDescriptors.loadStateDescriptor(), manager.getLoadStateDescriptor(testM)); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/LruCacheManagerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/LruCacheManagerTest.java index ea09f3000..abfac5f3f 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/LruCacheManagerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/LruCacheManagerTest.java @@ -96,11 +96,11 @@ public void testEvictByContextClassAndPrimaryKey() throws Exception { final LruCache lruCache = getLruCache(); final Descriptor descriptorOne = descriptor(CONTEXT_ONE); final Descriptor descriptorTwo = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptorTwo); + manager.add(testA.getUri(), testA, descriptors(descriptorTwo)); final OWLClassA duplicate = new OWLClassA(); duplicate.setUri(testA.getUri()); duplicate.setStringAttribute("Duplicate entity."); - manager.add(duplicate.getUri(), duplicate, descriptorOne); + manager.add(duplicate.getUri(), duplicate, descriptors(descriptorOne)); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptorTwo)); assertTrue(manager.contains(duplicate.getClass(), duplicate.getUri(), descriptorOne)); final int size = lruCache.size(); @@ -117,10 +117,10 @@ public void entryGetsEvictedWhenCacheIsFull() { final Descriptor descriptorTwo = descriptor(CONTEXT_TWO); this.manager = new LruCacheManager( Collections.singletonMap(JOPAPersistenceProperties.LRU_CACHE_CAPACITY, "2")); - manager.add(testA.getUri(), testA, descriptorOne); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptorOne)); - manager.add(testB.getUri(), testB, descriptorTwo); + manager.add(testB.getUri(), testB, descriptors(descriptorTwo)); assertFalse(manager.contains(testA.getClass(), testA.getUri(), descriptorOne)); assertTrue(manager.contains(testB.getClass(), testB.getUri(), descriptorTwo)); } @@ -131,15 +131,15 @@ public void leastRecentlyUsedEntryGetsEvictedWhenCacheIsFull_withContext() { final Descriptor descriptorTwo = descriptor(CONTEXT_TWO); this.manager = new LruCacheManager( Collections.singletonMap(JOPAPersistenceProperties.LRU_CACHE_CAPACITY, "4")); - manager.add(testA.getUri(), testA, descriptorOne); - manager.add(testB.getUri(), testB, descriptorTwo); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); + manager.add(testB.getUri(), testB, descriptors(descriptorTwo)); final OWLClassA aTwo = new OWLClassA(URI.create("http://aTwo")); - manager.add(aTwo.getUri(), aTwo, descriptorTwo); + manager.add(aTwo.getUri(), aTwo, descriptors(descriptorTwo)); manager.get(testA.getClass(), testA.getUri(), descriptorOne); manager.get(aTwo.getClass(), aTwo.getUri(), descriptorTwo); final OWLClassA newA = new OWLClassA(URI.create("http://newA")); - manager.add(newA.getUri(), newA, descriptor(null)); + manager.add(newA.getUri(), newA, descriptors(descriptor(null))); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptorOne)); assertTrue(manager.contains(aTwo.getClass(), aTwo.getUri(), descriptorTwo)); @@ -172,7 +172,7 @@ public void leastRecentlyUsedEntriesAreEvictedWhenCacheIsFull() { } else { retained.add(e); } - manager.add(e.getKey(), e.getValue(), descriptor(null)); + manager.add(e.getKey(), e.getValue(), descriptors(descriptor(null))); i++; } evicted.forEach(e -> assertFalse(manager.contains(e.getValue().getClass(), e.getKey(), descriptor(null)))); @@ -183,7 +183,7 @@ private LinkedHashMap generateItems(int capacity) { final int count = capacity * 5; final LinkedHashMap map = new LinkedHashMap<>(count); for (int i = 0; i < count; i++) { - int n = count % 4; + int n = count % (i + 1); switch (n) { case 0: final OWLClassA a = new OWLClassA(Generators.createIndividualIdentifier()); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/TtlCacheManagerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/TtlCacheManagerTest.java index 0c813ffa1..b101fb291 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/TtlCacheManagerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/cache/TtlCacheManagerTest.java @@ -19,7 +19,6 @@ import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.sessions.CacheManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,7 +28,10 @@ import java.util.Map; import java.util.concurrent.ScheduledExecutorService; -import static org.junit.jupiter.api.Assertions.*; +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.assertTrue; public class TtlCacheManagerTest extends AbstractCacheManagerTest { @@ -77,8 +79,8 @@ public void testEvictWithSweeper() throws Exception { initSweepableManager(); final Descriptor descriptorOne = descriptor(CONTEXT_ONE); final Descriptor descriptorTwo = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptorOne); - manager.add(testB.getUri(), testB, descriptorTwo); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); + manager.add(testB.getUri(), testB, descriptors(descriptorTwo)); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptorOne)); assertTrue(manager.contains(testB.getClass(), testB.getUri(), descriptorTwo)); // Give it enough time to sweep the cache @@ -92,8 +94,8 @@ public void testRefreshTTL() throws Exception { initSweepableManager(); final Descriptor descriptorOne = descriptor(CONTEXT_ONE); final Descriptor descriptorTwo = descriptor(CONTEXT_TWO); - manager.add(testA.getUri(), testA, descriptorOne); - manager.add(testB.getUri(), testB, descriptorTwo); + manager.add(testA.getUri(), testA, descriptors(descriptorOne)); + manager.add(testB.getUri(), testB, descriptors(descriptorTwo)); assertTrue(manager.contains(testA.getClass(), testA.getUri(), descriptorOne)); assertTrue(manager.contains(testB.getClass(), testB.getUri(), descriptorTwo)); // The cycle ensures that testA is refreshed and stays in the cache diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ChangeManagerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ChangeCalculatorTest.java similarity index 73% rename from jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ChangeManagerTest.java rename to jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ChangeCalculatorTest.java index 99ba599e7..e62fb7708 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ChangeManagerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ChangeCalculatorTest.java @@ -17,30 +17,66 @@ */ package cz.cvut.kbss.jopa.sessions.change; -import cz.cvut.kbss.jopa.environment.*; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassB; +import cz.cvut.kbss.jopa.environment.OWLClassC; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.OWLClassE; +import cz.cvut.kbss.jopa.environment.OWLClassF; +import cz.cvut.kbss.jopa.environment.OWLClassK; +import cz.cvut.kbss.jopa.environment.OWLClassM; +import cz.cvut.kbss.jopa.environment.OWLClassO; +import cz.cvut.kbss.jopa.environment.OWLClassQ; +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.environment.utils.TestEnvironmentUtils; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.model.metamodel.Metamodel; -import cz.cvut.kbss.jopa.sessions.ChangeManager; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingListProxy; +import cz.cvut.kbss.jopa.proxy.lazy.gen.LazyLoadingEntityProxyGenerator; import cz.cvut.kbss.jopa.sessions.MetamodelProvider; -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; 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.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; - -import static org.junit.jupiter.api.Assertions.*; +import java.util.Optional; +import java.util.Set; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +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; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class ChangeManagerTest { +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class ChangeCalculatorTest { private static final URI DEFAULT_CONTEXT = URI.create("http://defaultContext"); @@ -62,17 +98,16 @@ public class ChangeManagerTest { @Mock private MetamodelProvider providerMock; @Mock - private Metamodel metamodelMock; + private MetamodelImpl metamodelMock; private MetamodelMocks metamodelMocks; - private ChangeManager manager; + private ChangeCalculator sut; @BeforeEach public void setup() throws Exception { - MockitoAnnotations.openMocks(this); initInstances(); - manager = new ChangeManagerImpl(providerMock); + sut = new ChangeCalculator(providerMock); when(providerMock.isEntityType(any(Class.class))).thenAnswer(invocation -> { final Class cls = (Class) invocation.getArguments()[0]; return TestEnvironmentUtils.getManagedTypes().contains(cls); @@ -172,19 +207,19 @@ private void initClones() { @Test public void hasChangesOnNullReturnsFalse() { - assertFalse(manager.hasChanges(null, null)); + assertFalse(sut.hasChanges(null, null)); } @Test public void hasChangeWithChangedDataPropertyValueReturnsTrue() { testAClone.setStringAttribute("differentStringAttribute"); - assertTrue(manager.hasChanges(testA, testAClone)); + assertTrue(sut.hasChanges(testA, testAClone)); } @Test public void hasChangeWithoutChangeOnDataPropertyReturnsFalse() { testAClone.setStringAttribute(testA.getStringAttribute()); - assertFalse(manager.hasChanges(testA, testA)); + assertFalse(sut.hasChanges(testA, testA)); } @Test @@ -192,14 +227,14 @@ public void hasChangeOnDifferentObjectPropertyValueReturnsTrue() { final OWLClassA ref = new OWLClassA(Generators.createIndividualIdentifier()); ref.setStringAttribute(testA.getStringAttribute()); testDClone.setOwlClassA(ref); - assertTrue(manager.hasChanges(testD, testDClone)); + assertTrue(sut.hasChanges(testD, testDClone)); } @Test public void hasChangeFromEmptyCollectionToNonEmptyReturnsTrue() { testAClone.setTypes(typesCollection); testA.setTypes(new HashSet<>()); - assertTrue(manager.hasChanges(testA, testAClone)); + assertTrue(sut.hasChanges(testA, testAClone)); } @Test @@ -213,7 +248,7 @@ public void hasChangeWhenAddedItemToCollectionReturnsTrue() { changed.add(it.next()); } testAClone.setTypes(changed); - assertTrue(manager.hasChanges(testA, testAClone)); + assertTrue(sut.hasChanges(testA, testAClone)); } @Test @@ -227,31 +262,31 @@ public void hasChangeWhenItemRemovedFromCollectionReturnsTrue() { removed = true; } } - assertTrue(manager.hasChanges(testC, testCClone)); + assertTrue(sut.hasChanges(testC, testCClone)); } @Test public void hasChangeOnDataPropertyWhenOriginalValueWasNullReturnsTrue() { testA.setStringAttribute(null); testAClone.setStringAttribute("someString"); - assertTrue(manager.hasChanges(testA, testAClone)); + assertTrue(sut.hasChanges(testA, testAClone)); } @Test public void hasChangeAttributeValueChangeOnReferenceReturnsFalse() { testDClone.getOwlClassA().setStringAttribute("updatedString"); - assertFalse(manager.hasChanges(testD, testDClone)); + assertFalse(sut.hasChanges(testD, testDClone)); } @Test public void calculateChangesThrowsNPXForNullArgument() { - assertThrows(NullPointerException.class, () -> manager.calculateChanges(null)); + assertThrows(NullPointerException.class, () -> sut.calculateChanges(null)); } @Test public void calculateChangesForIdenticalObjectsAddsNoChangesToChangeSet() { final ObjectChangeSet chSet = createChangeSet(testA, testAClone); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertFalse(res); assertTrue(chSet.getChanges().isEmpty()); } @@ -261,7 +296,7 @@ public void calculateChangesRegistersChangeOnStringAttribute() { testAClone.setStringAttribute("updated"); ObjectChangeSet chSet = createChangeSet(testA, testAClone); assertTrue(chSet.getChanges().isEmpty()); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertFalse(chSet.getChanges().isEmpty()); verifyChangeSetContainsChangeOfAttribute(metamodelMocks.forOwlClassA().stringAttribute(), chSet); @@ -277,9 +312,9 @@ private void verifyChangeSetContainsChangeOfAttribute(FieldSpecification a public void calculateChangesRegistersChangeOnPrimitiveTypeAttribute() { final OWLClassM testMClone = new OWLClassM(); testMClone.setIntAttribute(testM.getIntAttribute() + 117); - ObjectChangeSet chSet = createChangeSet(testM, testMClone); + final ObjectChangeSet chSet = createChangeSet(testM, testMClone); assertTrue(chSet.getChanges().isEmpty()); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertFalse(chSet.getChanges().isEmpty()); verifyChangeSetContainsChangeOfAttribute(metamodelMocks.forOwlClassM().integerAttribute(), chSet); @@ -288,9 +323,9 @@ public void calculateChangesRegistersChangeOnPrimitiveTypeAttribute() { @Test public void calculateChangesRegistersChangeOnObjectPropertyAttribute() { testDClone.setOwlClassA(testAClone); - ObjectChangeSet chSet = createChangeSet(testD, testDClone); + final ObjectChangeSet chSet = createChangeSet(testD, testDClone); assertTrue(chSet.getChanges().isEmpty()); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertEquals(1, chSet.getChanges().size()); assertEquals(testAClone, chSet.getChanges().iterator().next().getNewValue()); @@ -303,12 +338,12 @@ public void calculateChangesRegistersChangeInCollection() { newCollection.remove(typesCollection.iterator().next()); newCollection.add("String"); testAClone.setTypes(newCollection); - ObjectChangeSet chSet = createChangeSet(testA, testAClone); + final ObjectChangeSet chSet = createChangeSet(testA, testAClone); assertTrue(chSet.getChanges().isEmpty()); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertEquals(1, chSet.getChanges().size()); - assertTrue(((Set) chSet.getChanges().iterator().next().getNewValue()).contains("String")); + assertThat((Set) chSet.getChanges().iterator().next().getNewValue(), hasItem("String")); } @Test @@ -319,9 +354,9 @@ public void calculateChangesRegistersMultipleChanges() { newCollection.add("String"); testAClone.setTypes(newCollection); testAClone.setStringAttribute("AnotherStringAttribute"); - ObjectChangeSet chSet = createChangeSet(testA, testAClone); + final ObjectChangeSet chSet = createChangeSet(testA, testAClone); assertTrue(chSet.getChanges().isEmpty()); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertEquals(2, chSet.getChanges().size()); verifyChangeSetContainsChangeOfAttribute(metamodelMocks.forOwlClassA().stringAttribute(), chSet); @@ -330,10 +365,10 @@ public void calculateChangesRegistersMultipleChanges() { @Test public void calculateChangesRegistersChangeWhenValueIsSetToNull() { - ObjectChangeSet chSet = createChangeSet(testA, testAClone); + final ObjectChangeSet chSet = createChangeSet(testA, testAClone); testAClone.setStringAttribute(null); assertTrue(chSet.getChanges().isEmpty()); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertFalse(chSet.getChanges().isEmpty()); assertNotNull(chSet); @@ -344,7 +379,7 @@ public void calculateChangesRegistersChangeWhenValueIsSetToNull() { public void calculateChangesRegistersChangeWhenValueIsSetToNonNull() { testA.setTypes(null); final ObjectChangeSet chSet = createChangeSet(testA, testAClone); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertEquals(1, chSet.getChanges().size()); assertNotNull(chSet.getChanges().iterator().next().getNewValue()); @@ -353,9 +388,9 @@ public void calculateChangesRegistersChangeWhenValueIsSetToNonNull() { @Test public void calculateChangesRegistersItemRemovalFromReferenceCollection() { testCClone.getReferencedList().remove(4); - ObjectChangeSet chSet = createChangeSet(testC, testCClone); + final ObjectChangeSet chSet = createChangeSet(testC, testCClone); assertTrue(chSet.getChanges().isEmpty()); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); verifyChangeSetContainsChangeOfAttribute(metamodelMocks.forOwlClassC().referencedListAtt(), chSet); assertEquals(1, chSet.getChanges().size()); @@ -365,9 +400,9 @@ public void calculateChangesRegistersItemRemovalFromReferenceCollection() { @Test public void calculateChangesRegistersItemAdditionToReferenceCollection() { testCClone.getReferencedList().add(testA); - ObjectChangeSet chSet = createChangeSet(testC, testCClone); + final ObjectChangeSet chSet = createChangeSet(testC, testCClone); assertTrue(chSet.getChanges().isEmpty()); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertEquals(1, chSet.getChanges().size()); final ChangeRecord r = chSet.getChanges().iterator().next(); @@ -381,9 +416,9 @@ public void calculateChangesRegistersItemAdditionToReferenceCollection() { public void calculateChangesRegistersItemReplacementInReferenceCollection() { testCClone.getReferencedList().remove(3); testCClone.getReferencedList().add(testA); - ObjectChangeSet chSet = createChangeSet(testC, testCClone); + final ObjectChangeSet chSet = createChangeSet(testC, testCClone); assertTrue(chSet.getChanges().isEmpty()); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); verifyChangeSetContainsChangeOfAttribute(metamodelMocks.forOwlClassC().referencedListAtt(), chSet); assertEquals(1, chSet.getChanges().size()); @@ -396,7 +431,7 @@ public void calculateChangesRegistersItemReplacementInReferenceCollection() { public void calculateChangesRegistersMapKeyAddition() { testBClone.getProperties().put("newProperty", Collections.singleton("propVal")); final ObjectChangeSet chSet = createChangeSet(testB, testBClone); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertEquals(1, chSet.getChanges().size()); verifyChangeSetContainsChangeOfAttribute(metamodelMocks.forOwlClassB().propertiesSpec(), chSet); @@ -407,7 +442,7 @@ public void calculateChangesRegistersMapValueChange() { final String key = testB.getProperties().keySet().iterator().next(); testBClone.getProperties().put(key, Collections.singleton("propVal")); final ObjectChangeSet chSet = createChangeSet(testB, testBClone); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertEquals(1, chSet.getChanges().size()); verifyChangeSetContainsChangeOfAttribute(metamodelMocks.forOwlClassB().propertiesSpec(), chSet); @@ -418,14 +453,14 @@ public void calculateChangesRegistersMapValueAddition() { final String key = testB.getProperties().keySet().iterator().next(); testBClone.getProperties().get(key).add("addedValue"); final ObjectChangeSet chSet = createChangeSet(testB, testBClone); - final boolean res = manager.calculateChanges(chSet); + final boolean res = sut.calculateChanges(chSet); assertTrue(res); assertEquals(1, chSet.getChanges().size()); verifyChangeSetContainsChangeOfAttribute(metamodelMocks.forOwlClassB().propertiesSpec(), chSet); } private ObjectChangeSet createChangeSet(Object orig, Object clone) { - return TestEnvironmentUtils.createObjectChangeSet(orig, clone, DEFAULT_CONTEXT); + return ChangeSetFactory.createObjectChangeSet(orig, clone, new EntityDescriptor(DEFAULT_CONTEXT)); } @Test @@ -437,7 +472,7 @@ public void twoSetsWithSameElementsButDifferentOrderHaveNoChanges() { final Set newTypes = new LinkedHashSet<>(lst); testAClone.setTypes(newTypes); assertNotEquals(typesCollection.iterator().next(), newTypes.iterator().next()); - final boolean res = manager.hasChanges(testA, testAClone); + final boolean res = sut.hasChanges(testA, testAClone); assertFalse(res); } @@ -446,12 +481,12 @@ public void twoSetsWithManagedElementsWithSameIdentifiersHaveNoChanges() { final OWLClassF testF = new OWLClassF(); final OWLClassF cloneF = new OWLClassF(); initFAndClone(testF, cloneF); - final boolean res = manager.hasChanges(testF, cloneF); + final boolean res = sut.hasChanges(testF, cloneF); assertFalse(res); } private void initFAndClone(OWLClassF orig, OWLClassF clone) { - final URI uri = URI.create("http://krizik.felk.cvut.cz/ontologies#testF"); + final URI uri = URI.create(Vocabulary.INDIVIDUAL_BASE + "testF"); orig.setUri(uri); clone.setUri(uri); orig.setSimpleSet(new HashSet<>()); @@ -459,7 +494,7 @@ private void initFAndClone(OWLClassF orig, OWLClassF clone) { orig.getSimpleSet().add(testA); testAClone.setStringAttribute(testA.getStringAttribute()); clone.getSimpleSet().add(testAClone); - final URI aUri = URI.create("http://krizik.felk.cvut.cz/ontologies#testAA"); + final URI aUri = URI.create(Vocabulary.INDIVIDUAL_BASE + "testAA"); final OWLClassA extraA = new OWLClassA(aUri); extraA.setStringAttribute("string"); orig.getSimpleSet().add(extraA); @@ -480,7 +515,7 @@ public void twoSetsWithManagedElementsOneElementReplacedWithNewWithoutIdHaveChan remove.next(); remove.remove(); cloneF.getSimpleSet().add(added); - final boolean res = manager.hasChanges(testF, cloneF); + final boolean res = sut.hasChanges(testF, cloneF); assertTrue(res); } @@ -491,7 +526,7 @@ public void hasChangesIgnoresTransientFieldChanges() { testOClone.setStringAttribute(testO.getStringAttribute()); testOClone.setTransientField("Change!"); - final boolean res = manager.hasChanges(testO, testOClone); + final boolean res = sut.hasChanges(testO, testOClone); assertFalse(res); } @@ -503,7 +538,7 @@ public void calculateChangesIgnoresTransientFieldChanges() { testOClone.setTransientFieldWithAnnotation("Change!"); final ObjectChangeSet changeSet = createChangeSet(testO, testOClone); - final boolean res = manager.calculateChanges(changeSet); + final boolean res = sut.calculateChanges(changeSet); assertFalse(res); assertTrue(changeSet.getChanges().isEmpty()); } @@ -518,7 +553,7 @@ public void calculateChangesDetectsChangesInMappedSuperclassFields() { testQClone.setOwlClassA(testA); final ObjectChangeSet changeSet = createChangeSet(testQ, testQClone); - final boolean res = manager.calculateChanges(changeSet); + final boolean res = sut.calculateChanges(changeSet); assertTrue(res); final Set changes = changeSet.getChanges(); assertEquals(3, changes.size()); @@ -541,15 +576,59 @@ public void calculateChangesDetectsChangesInMappedSuperclassObjectPropertyField( testQClone.setParentString(testQ.getParentString()); testQClone.setLabel(testQ.getLabel()); final OWLClassA newA = new OWLClassA(); - newA.setUri(URI.create("http://krizik.felk.cvut.cz/ontologies/jopa#newA")); + newA.setUri(URI.create(Vocabulary.INDIVIDUAL_BASE + "newA")); testQClone.setOwlClassA(newA); final ObjectChangeSet changeSet = createChangeSet(testQ, testQClone); - final boolean res = manager.calculateChanges(changeSet); + final boolean res = sut.calculateChanges(changeSet); assertTrue(res); final Set changes = changeSet.getChanges(); assertEquals(1, changes.size()); verifyChangeSetContainsChangeOfAttribute(metamodelMocks.forOwlClassQ().qOwlClassAAtt(), changeSet); assertEquals(newA, changes.iterator().next().getNewValue()); } + + @Test + void calculateChangesDoesNotDetectChangeWhenOriginalValueIsNullAndCloneValueIsLazyLoadingProxy() throws Exception { + final OWLClassK original = new OWLClassK(Generators.createIndividualIdentifier()); + final OWLClassK clone = new OWLClassK(original.getUri()); + final LazyLoadingEntityProxyGenerator lazyProxyGenerator = new LazyLoadingEntityProxyGenerator(); + final Class lazyProxyCls = lazyProxyGenerator.generate(OWLClassE.class); + clone.setOwlClassE(lazyProxyCls.getDeclaredConstructor().newInstance()); + final ObjectChangeSet changeSet = createChangeSet(original, clone); + final boolean res = sut.calculateChanges(changeSet); + assertFalse(res); + } + + @Test + void calculateChangesSkipsIdentifier() { + final OWLClassA original = Generators.generateOwlClassAInstance(); + final OWLClassA clone = new OWLClassA(Generators.createIndividualIdentifier()); + clone.setStringAttribute(original.getStringAttribute()); + clone.setTypes(new HashSet<>(original.getTypes())); + final ObjectChangeSet changeSet = createChangeSet(original, clone); + final boolean res = sut.calculateChanges(changeSet); + assertFalse(res); + } + + @Test + void hasChangesSkipsIdentifier() { + final OWLClassA original = Generators.generateOwlClassAInstance(); + final OWLClassA clone = new OWLClassA(Generators.createIndividualIdentifier()); + clone.setStringAttribute(original.getStringAttribute()); + clone.setTypes(new HashSet<>(original.getTypes())); + assertFalse(sut.hasChanges(original, clone)); + } + + @Test + void calculateChangesSkipsLazyLoadedCollectionValueWhenOriginalIsEmptyCollection() { + final OWLClassC original = new OWLClassC(Generators.createIndividualIdentifier()); + original.setSimpleList(Collections.emptyList()); + final OWLClassC clone = new OWLClassC(original.getUri()); + final UnitOfWork uow = mock(UnitOfWork.class); + clone.setSimpleList(new LazyLoadingListProxy<>(clone, metamodelMocks.forOwlClassC().simpleListAtt(), uow)); + final ObjectChangeSet changeSet = createChangeSet(original, clone); + assertFalse(sut.calculateChanges(changeSet)); + verify(uow, never()).loadEntityField(any(), any()); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/CollectionChangeDetectorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/CollectionChangeDetectorTest.java index aca8f09b0..b16bf308d 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/CollectionChangeDetectorTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/CollectionChangeDetectorTest.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; -import cz.cvut.kbss.jopa.model.metamodel.Metamodel; +import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.sessions.MetamodelProvider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,7 +31,13 @@ import org.mockito.quality.Strictness; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -45,7 +51,7 @@ public class CollectionChangeDetectorTest { private MetamodelProvider providerMock; @Mock - private Metamodel metamodel; + private MetamodelImpl metamodel; private CollectionChangeDetector changeDetector; diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ManagedTypeChangeDetectorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ManagedTypeChangeDetectorTest.java index ebc5a8a15..df19ddc1d 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ManagedTypeChangeDetectorTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ManagedTypeChangeDetectorTest.java @@ -2,9 +2,9 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.utils.Generators; -import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; import cz.cvut.kbss.jopa.model.metamodel.Identifier; -import cz.cvut.kbss.jopa.model.metamodel.Metamodel; import cz.cvut.kbss.jopa.sessions.MetamodelProvider; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; @@ -33,9 +33,9 @@ class ManagedTypeChangeDetectorTest { @ParameterizedTest @MethodSource("testParams") void hasChangesReturnsChangesWhenIdentifierDiffers(OWLClassA clone, OWLClassA original, Boolean expected) throws Exception { - final Metamodel metamodel = mock(Metamodel.class); + final MetamodelImpl metamodel = mock(MetamodelImpl.class); when(metamodelProvider.getMetamodel()).thenReturn(metamodel); - final EntityType et = mock(EntityType.class); + final IdentifiableEntityType et = mock(IdentifiableEntityType.class); when(metamodel.entity(OWLClassA.class)).thenReturn(et); final Identifier idAtt = mock(Identifier.class); when(et.getIdentifier()).thenReturn(idAtt); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSetTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSetTest.java index e10ec72e5..967b6725a 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSetTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSetTest.java @@ -17,13 +17,13 @@ */ package cz.cvut.kbss.jopa.sessions.change; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.net.URI; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -33,26 +33,39 @@ public class ObjectChangeSetTest { - private String testObject; - private String testClone; + private static final OWLClassA original = new OWLClassA(URI.create(Vocabulary.c_OwlClassA)); - @BeforeEach - public void setUp() { - this.testObject = "TEST"; - this.testClone = "TEST"; + private static final OWLClassA clone = new OWLClassA(URI.create(Vocabulary.c_OwlClassA)); + + private final ObjectChangeSet sut = new ObjectChangeSet(original, clone, new EntityDescriptor()); + + @Test + public void addChangeRecordAddsChangeRecordToChanges() { + final String attName = "stringAttribute"; + final FieldSpecification fs = mock(FieldSpecification.class); + when(fs.getName()).thenReturn(attName); + ChangeRecord record = new ChangeRecord(fs, "newValue"); + sut.addChangeRecord(record); + final Optional result = sut.getChanges().stream().filter(ch -> ch.getAttribute().equals(fs)) + .findAny(); + + assertTrue(result.isPresent()); + assertEquals(record.getNewValue(), result.get().getNewValue()); } @Test - public void testAddChangeRecord() { - final String attName = "testAtt"; + void addChangeRecordOverwritesPreviousChangeRecordOfAttribute() { + final String attName = "stringAttribute"; final FieldSpecification fs = mock(FieldSpecification.class); when(fs.getName()).thenReturn(attName); - ChangeRecord record = new ChangeRecordImpl(fs, testObject); - ObjectChangeSet chs = new ObjectChangeSetImpl(testObject, testClone, new EntityDescriptor()); - chs.addChangeRecord(record); - final Optional result = chs.getChanges().stream().filter(ch -> ch.getAttribute().equals(fs)) - .findAny(); + ChangeRecord firstRecord = new ChangeRecord(fs, "firstValue"); + sut.addChangeRecord(firstRecord); + final ChangeRecord secondRecord = new ChangeRecord(fs, "secondValue"); + sut.addChangeRecord(secondRecord); + + final Optional result = sut.getChanges().stream().filter(ch -> ch.getAttribute().equals(fs)) + .findAny(); assertTrue(result.isPresent()); - assertEquals(testObject, result.get().getNewValue()); + assertEquals(secondRecord.getNewValue(), result.get().getNewValue()); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/UnitOfWorkChangeSetTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/UnitOfWorkChangeSetTest.java index 5af0a29e5..7df42760a 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/UnitOfWorkChangeSetTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/change/UnitOfWorkChangeSetTest.java @@ -17,12 +17,14 @@ */ package cz.cvut.kbss.jopa.sessions.change; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkChangeSet; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.HashSet; + import static org.junit.jupiter.api.Assertions.*; public class UnitOfWorkChangeSetTest { @@ -37,58 +39,49 @@ public void setUp() { this.testObject = "TEST"; final String testClone = "TEST"; this.changeSet = ChangeSetFactory.createObjectChangeSet(testObject, testClone, new EntityDescriptor()); - uowChangeSet = new UnitOfWorkChangeSetImpl(); + uowChangeSet = new UnitOfWorkChangeSet(); } @Test public void testAddObjectChangeSet() { uowChangeSet.addObjectChangeSet(changeSet); assertEquals(1, uowChangeSet.getExistingObjectsChanges().size()); - ObjectChangeSet res = uowChangeSet.getExistingObjectsChanges().iterator().next(); + Change res = uowChangeSet.getExistingObjectsChanges().iterator().next(); assertSame(changeSet, res); assertTrue(uowChangeSet.hasChanges()); } - /** - * This tests the fact that if we pass object change set for a new object, the UoWChangeSet should forward the call - * to the addNewObjectChangeSet - */ - @Test - public void testAddObjectChangeSetWithNew() { - changeSet.setNew(true); - uowChangeSet.addObjectChangeSet(changeSet); - assertEquals(1, uowChangeSet.getNewObjects().size()); - ObjectChangeSet res = uowChangeSet.getNewObjects().iterator().next(); - assertSame(changeSet, res); - assertTrue(uowChangeSet.hasNew()); - } - @Test public void testAddDeletedObject() { - uowChangeSet.addDeletedObjectChangeSet(changeSet); + final OWLClassA original = Generators.generateOwlClassAInstance(); + final OWLClassA clone = new OWLClassA(original.getUri()); + clone.setStringAttribute(original.getStringAttribute()); + clone.setTypes(new HashSet<>(original.getTypes())); + final DeleteObjectChange deleteChange = ChangeSetFactory.createDeleteObjectChange(clone, original, new EntityDescriptor()); + uowChangeSet.addDeletedObjectChangeSet(deleteChange); assertEquals(1, uowChangeSet.getDeletedObjects().size()); - ObjectChangeSet res = uowChangeSet.getDeletedObjects().iterator().next(); - Object result = res.getChangedObject(); - assertEquals(testObject, result); + DeleteObjectChange res = uowChangeSet.getDeletedObjects().iterator().next(); + Object result = res.getOriginal(); + assertEquals(original, result); assertTrue(uowChangeSet.hasDeleted()); assertTrue(uowChangeSet.hasChanges()); } @Test public void testAddNewObjectChangeSet() { - changeSet.setNew(true); - uowChangeSet.addNewObjectChangeSet(changeSet); + final NewObjectChange change = ChangeSetFactory.createNewObjectChange(testObject, new EntityDescriptor()); + uowChangeSet.addNewObjectChangeSet(change); assertTrue(uowChangeSet.hasChanges()); assertEquals(1, uowChangeSet.getNewObjects().size()); - ObjectChangeSet res = uowChangeSet.getNewObjects().iterator().next(); - assertSame(changeSet, res); + Change res = uowChangeSet.getNewObjects().iterator().next(); + assertSame(change, res); assertTrue(uowChangeSet.hasNew()); } @Test public void getExistingObjectChangesReturnsChangeSetForTheSpecifiedOriginal() { uowChangeSet.addObjectChangeSet(changeSet); - final ObjectChangeSet result = uowChangeSet.getExistingObjectChanges(testObject); + final Change result = uowChangeSet.getExistingObjectChanges(testObject); assertNotNull(result); assertSame(changeSet, result); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptorFactoryTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactoryTest.java similarity index 71% rename from jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptorFactoryTest.java rename to jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactoryTest.java index 2a220758f..f66e145a0 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptorFactoryTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorFactoryTest.java @@ -23,12 +23,16 @@ import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.model.LoadState; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingListProxy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.mock; -class InstanceDescriptorFactoryTest { +class LoadStateDescriptorFactoryTest { private MetamodelMocks metamodelMocks; @@ -40,7 +44,7 @@ void setUp() throws Exception { @Test void createNotLoadedCreatesInstanceDescriptorWithAttributesInNotLoadedState() { final OWLClassM instance = new OWLClassM(); - final InstanceDescriptor result = InstanceDescriptorFactory + final LoadStateDescriptor result = LoadStateDescriptorFactory .createNotLoaded(instance, metamodelMocks.forOwlClassM().entityType()); assertNotNull(result); metamodelMocks.forOwlClassM().entityType().getAttributes() @@ -51,7 +55,7 @@ void createNotLoadedCreatesInstanceDescriptorWithAttributesInNotLoadedState() { @Test void createAllLoadedCreatesInstanceDescriptorWithAllAttributesInLoadedState() { final OWLClassA instance = Generators.generateOwlClassAInstance(); - final InstanceDescriptor result = InstanceDescriptorFactory + final LoadStateDescriptor result = LoadStateDescriptorFactory .createAllLoaded(instance, metamodelMocks.forOwlClassA().entityType()); assertNotNull(result); metamodelMocks.forOwlClassA().entityType().getFieldSpecifications() @@ -62,7 +66,7 @@ void createAllLoadedCreatesInstanceDescriptorWithAllAttributesInLoadedState() { void createCreatesInstanceDescriptorWithLoadedSetForNonNullAttributes() { final OWLClassA instance = Generators.generateOwlClassAInstance(); instance.setTypes(null); - final InstanceDescriptor result = InstanceDescriptorFactory + final LoadStateDescriptor result = LoadStateDescriptorFactory .create(instance, metamodelMocks.forOwlClassA().entityType()); assertNotNull(result); assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassA().identifier())); @@ -74,7 +78,7 @@ void createCreatesInstanceDescriptorWithLoadedSetForNullEagerlyFetchedAttributes final OWLClassA instance = Generators.generateOwlClassAInstance(); instance.setStringAttribute(null); instance.setTypes(null); - final InstanceDescriptor result = InstanceDescriptorFactory + final LoadStateDescriptor result = LoadStateDescriptorFactory .create(instance, metamodelMocks.forOwlClassA().entityType()); assertNotNull(result); assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassA().identifier())); @@ -85,7 +89,7 @@ void createCreatesInstanceDescriptorWithLoadedSetForNullEagerlyFetchedAttributes @Test void createCreatesInstanceDescriptorWithUnknownSetForNullLazilyFetchedAttributes() { final OWLClassC instance = new OWLClassC(Generators.createIndividualIdentifier()); - final InstanceDescriptor result = InstanceDescriptorFactory + final LoadStateDescriptor result = LoadStateDescriptorFactory .create(instance, metamodelMocks.forOwlClassC().entityType()); assertNotNull(result); assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassC().identifier())); @@ -96,13 +100,25 @@ void createCreatesInstanceDescriptorWithUnknownSetForNullLazilyFetchedAttributes @Test void createCopyCopiesStateFromSpecifiedDescriptor() { final OWLClassA instance = Generators.generateOwlClassAInstance(); - final InstanceDescriptor original = new InstanceDescriptor<>(instance, - metamodelMocks.forOwlClassA().entityType()); + final LoadStateDescriptor original = new LoadStateDescriptor<>(instance, + metamodelMocks.forOwlClassA().entityType(), LoadState.LOADED); final OWLClassA otherInstance = Generators.generateOwlClassAInstance(); - final InstanceDescriptor result = InstanceDescriptorFactory.createCopy(otherInstance, original); + final LoadStateDescriptor result = LoadStateDescriptorFactory.createCopy(otherInstance, original); assertNotNull(result); assertSame(otherInstance, result.getInstance()); assertEquals(original.isLoaded(), result.isLoaded()); } -} \ No newline at end of file + + @Test + void createCreatesDescriptorWithNotLoadedForLazyLoadedAttributesContainingProxyInstances() { + final OWLClassC instance = new OWLClassC(Generators.createIndividualIdentifier()); + instance.setSimpleList(mock(LazyLoadingListProxy.class)); + final LoadStateDescriptor result = LoadStateDescriptorFactory + .create(instance, metamodelMocks.forOwlClassC().entityType()); + assertNotNull(result); + assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassC().identifier())); + assertEquals(LoadState.LOADED, result.isLoaded(metamodelMocks.forOwlClassC().referencedListAtt())); + assertEquals(LoadState.NOT_LOADED, result.isLoaded(metamodelMocks.forOwlClassC().simpleListAtt())); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorTest.java similarity index 73% rename from jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptorTest.java rename to jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorTest.java index a86171589..bb3860029 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/InstanceDescriptorTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/descriptor/LoadStateDescriptorTest.java @@ -28,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; -class InstanceDescriptorTest { +class LoadStateDescriptorTest { private MetamodelMocks metamodelMocks; @@ -38,32 +38,32 @@ void setUp() throws Exception { } @Test - void constructorInitializesAllDataPropertyAttributesAsNotLoaded() { + void constructorInitializesAllDataPropertyAttributesWithSpecifiedState() { final OWLClassM instance = new OWLClassM(); - final InstanceDescriptor sut = new InstanceDescriptor<>(instance, - metamodelMocks.forOwlClassM().entityType()); + final LoadStateDescriptor sut = new LoadStateDescriptor<>(instance, + metamodelMocks.forOwlClassM().entityType(), LoadState.UNKNOWN); metamodelMocks.forOwlClassM().entityType().getAttributes() - .forEach(fs -> assertEquals(LoadState.NOT_LOADED, sut.isLoaded(fs))); + .forEach(fs -> assertEquals(LoadState.UNKNOWN, sut.isLoaded(fs))); } @Test void constructorSetsIdentifierAsLoaded() { final OWLClassM instance = new OWLClassM(); - final InstanceDescriptor sut = new InstanceDescriptor<>(instance, - metamodelMocks.forOwlClassM().entityType()); + final LoadStateDescriptor sut = new LoadStateDescriptor<>(instance, + metamodelMocks.forOwlClassM().entityType(), LoadState.NOT_LOADED); assertEquals(LoadState.LOADED, sut.isLoaded(metamodelMocks.forOwlClassM().identifier())); } @Test void copyConstructorCreatesDescriptorForDifferentInstanceWithSameLoadedState() { final OWLClassM instance = new OWLClassM(); - final InstanceDescriptor sut = new InstanceDescriptor<>(instance, - metamodelMocks.forOwlClassM().entityType()); + final LoadStateDescriptor sut = new LoadStateDescriptor<>(instance, + metamodelMocks.forOwlClassM().entityType(), LoadState.UNKNOWN); sut.setLoaded(metamodelMocks.forOwlClassM().booleanAttribute(), LoadState.LOADED); sut.setLoaded(metamodelMocks.forOwlClassM().integerAttribute(), LoadState.LOADED); final OWLClassM anotherInstance = new OWLClassM(); - final InstanceDescriptor result = new InstanceDescriptor<>(anotherInstance, sut); + final LoadStateDescriptor result = new LoadStateDescriptor<>(anotherInstance, sut); metamodelMocks.forOwlClassM().entityType().getFieldSpecifications() .forEach(fs -> assertEquals(sut.isLoaded(fs), result.isLoaded(fs))); assertSame(anotherInstance, result.getInstance()); @@ -73,8 +73,8 @@ void copyConstructorCreatesDescriptorForDifferentInstanceWithSameLoadedState() { @Test void isLoadedByAttributeReturnsLoadedAllEagerAttributesAreLoaded() { final OWLClassA instance = new OWLClassA(); - final InstanceDescriptor sut = new InstanceDescriptor<>(instance, - metamodelMocks.forOwlClassA().entityType()); + final LoadStateDescriptor sut = new LoadStateDescriptor<>(instance, + metamodelMocks.forOwlClassA().entityType(), LoadState.UNKNOWN); sut.setLoaded(metamodelMocks.forOwlClassA().identifier(), LoadState.LOADED); sut.setLoaded(metamodelMocks.forOwlClassA().stringAttribute(), LoadState.LOADED); sut.setLoaded(metamodelMocks.forOwlClassA().typesSpec(), LoadState.LOADED); @@ -84,8 +84,8 @@ void isLoadedByAttributeReturnsLoadedAllEagerAttributesAreLoaded() { @Test void isLoadedReturnsNotLoadedWhenAtLeastOneEagerlyFetchedAttributeIsNotLoaded() { final OWLClassA instance = new OWLClassA(); - final InstanceDescriptor sut = new InstanceDescriptor<>(instance, - metamodelMocks.forOwlClassA().entityType()); + final LoadStateDescriptor sut = new LoadStateDescriptor<>(instance, + metamodelMocks.forOwlClassA().entityType(), LoadState.NOT_LOADED); sut.setLoaded(metamodelMocks.forOwlClassA().stringAttribute(), LoadState.LOADED); // types are eagerly fetched and are not loaded @@ -95,8 +95,8 @@ void isLoadedReturnsNotLoadedWhenAtLeastOneEagerlyFetchedAttributeIsNotLoaded() @Test void isLoadedReturnsUnknownWhenAtLeastOneEagerlyFetchedAttributeIsUnknownAndOthersAreLoaded() { final OWLClassM instance = new OWLClassM(); - final InstanceDescriptor sut = new InstanceDescriptor<>(instance, - metamodelMocks.forOwlClassM().entityType()); + final LoadStateDescriptor sut = new LoadStateDescriptor<>(instance, + metamodelMocks.forOwlClassM().entityType(), LoadState.UNKNOWN); metamodelMocks.forOwlClassM().entityType().getFieldSpecifications() .forEach(fs -> sut.setLoaded(fs, LoadState.LOADED)); sut.setLoaded(metamodelMocks.forOwlClassM().booleanAttribute(), LoadState.UNKNOWN); @@ -107,10 +107,10 @@ void isLoadedReturnsUnknownWhenAtLeastOneEagerlyFetchedAttributeIsUnknownAndOthe @Test void isLoadedReturnsLoadedWhenEagerlyFetchedAttributesAreLoadedAndLazilyIsNotLoaded() { final OWLClassC instance = new OWLClassC(); - final InstanceDescriptor sut = new InstanceDescriptor<>(instance, - metamodelMocks.forOwlClassC().entityType()); + final LoadStateDescriptor sut = new LoadStateDescriptor<>(instance, + metamodelMocks.forOwlClassC().entityType(), LoadState.UNKNOWN); sut.setLoaded(metamodelMocks.forOwlClassC().referencedListAtt(), LoadState.LOADED); assertEquals(LoadState.LOADED, sut.isLoaded()); } -} \ No newline at end of file +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/CollectionValueMergerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/CollectionValueMergerTest.java index b90e9360b..6ee170b7c 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/CollectionValueMergerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/CollectionValueMergerTest.java @@ -28,8 +28,8 @@ import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; -import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -45,7 +45,11 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import static org.junit.jupiter.api.Assertions.*; +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.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -55,7 +59,7 @@ public class CollectionValueMergerTest { @Mock - private UnitOfWorkImpl uow; + private UnitOfWork uow; @Mock private MetamodelImpl metamodel; @@ -93,7 +97,7 @@ public void mergeMergesUpdatedValuesIntoOriginalSetOfNonManagedTypes() { final EntityType et = metamodel.entity(OWLClassA.class); final FieldSpecification typesSpec = et.getTypes(); - sut.mergeValue(orig, new ChangeRecordImpl(typesSpec, merge.getTypes()), descriptor); + sut.mergeValue(orig, new ChangeRecord(typesSpec, merge.getTypes()), descriptor); assertTrue(orig.getTypes().contains(addedType)); assertFalse(orig.getTypes().contains(removedType)); } @@ -106,7 +110,7 @@ public void mergeReplacesNullCollectionWithMergedOne() { final EntityType et = metamodel.entity(OWLClassA.class); final FieldSpecification typesSpec = et.getTypes(); - sut.mergeValue(orig, new ChangeRecordImpl(typesSpec, merge.getTypes()), descriptor); + sut.mergeValue(orig, new ChangeRecord(typesSpec, merge.getTypes()), descriptor); assertNotNull(orig.getTypes()); assertEquals(orig.getTypes(), merge.getTypes()); } @@ -119,7 +123,7 @@ public void mergeReplacesOriginalCollectionWithNullWhenMergedHasNull() { final EntityType et = metamodel.entity(OWLClassA.class); final FieldSpecification typesSpec = et.getTypes(); - sut.mergeValue(orig, new ChangeRecordImpl(typesSpec, merge.getTypes()), descriptor); + sut.mergeValue(orig, new ChangeRecord(typesSpec, merge.getTypes()), descriptor); assertNull(orig.getTypes()); } @@ -151,7 +155,7 @@ public void mergeLoadsReferencesUsedInMergedCollectionFromStorage() throws Excep final FieldSpecification att = et .getFieldSpecification(OWLClassF.getSimpleSetField().getName()); - sut.mergeValue(orig, new ChangeRecordImpl(att, merged.getSimpleSet()), descriptor); + sut.mergeValue(orig, new ChangeRecord(att, merged.getSimpleSet()), descriptor); assertEquals(merged.getSimpleSet().size(), orig.getSimpleSet().size()); merged.getSimpleSet().forEach(a -> verify(uow).readObject(OWLClassA.class, a.getUri(), descriptor)); } @@ -167,7 +171,7 @@ public void mergeReplacesOriginalCollectionWithEmptyOneWhenMergedCollectionIsEmp final FieldSpecification att = et .getFieldSpecification(OWLClassF.getSimpleSetField().getName()); - sut.mergeValue(orig, new ChangeRecordImpl(att, merged.getSimpleSet()), descriptor); + sut.mergeValue(orig, new ChangeRecord(att, merged.getSimpleSet()), descriptor); assertNotNull(orig.getSimpleSet()); assertTrue(orig.getSimpleSet().isEmpty()); } @@ -195,7 +199,7 @@ public void mergeLoadsReferencesUsedInMergedListFromStorage() throws Exception { final FieldSpecification att = et .getFieldSpecification(OWLClassC.getRefListField().getName()); - sut.mergeValue(orig, new ChangeRecordImpl(att, merged.getReferencedList()), descriptor); + sut.mergeValue(orig, new ChangeRecord(att, merged.getReferencedList()), descriptor); assertEquals(merged.getReferencedList().size(), orig.getReferencedList().size()); merged.getReferencedList().forEach(a -> verify(uow).readObject(OWLClassA.class, a.getUri(), descriptor)); } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/DefaultValueMergerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/DefaultValueMergerTest.java index 3860f566a..81f4765d4 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/DefaultValueMergerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/DefaultValueMergerTest.java @@ -22,7 +22,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.sessions.change.ChangeRecordImpl; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.junit.jupiter.MockitoSettings; @@ -51,7 +51,7 @@ void setUp() throws Exception { void mergeValueSetsValueFromChangeRecordDirectlyOnTargetObject() { final OWLClassA target = Generators.generateOwlClassAInstance(); final String newValue = "new test value"; - sut.mergeValue(target, new ChangeRecordImpl(metamodelMocks.forOwlClassA().stringAttribute(), newValue), descriptor); + sut.mergeValue(target, new ChangeRecord(metamodelMocks.forOwlClassA().stringAttribute(), newValue), descriptor); assertEquals(newValue, target.getStringAttribute()); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/ManagedTypeValueMergerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/ManagedTypeValueMergerTest.java index 5a29eea90..2aa70cdf5 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/ManagedTypeValueMergerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/ManagedTypeValueMergerTest.java @@ -25,9 +25,8 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; -import cz.cvut.kbss.jopa.sessions.ChangeRecord; -import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl; -import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -36,7 +35,9 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.verify; @@ -47,7 +48,7 @@ public class ManagedTypeValueMergerTest { @Mock - private UnitOfWorkImpl uow; + private UnitOfWork uow; @Mock private MetamodelImpl metamodel; @@ -80,7 +81,7 @@ public void mergeValueLoadsInstanceFromRepository() { final OWLClassD target = new OWLClassD(Generators.createIndividualIdentifier()); when(uow.readObject(OWLClassA.class, merged.getUri(), descriptor)).thenReturn(orig); - sut.mergeValue(target, new ChangeRecordImpl(refASpec, merged), descriptor); + sut.mergeValue(target, new ChangeRecord(refASpec, merged), descriptor); verify(uow).readObject(OWLClassA.class, merged.getUri(), descriptor); assertSame(orig, target.getOwlClassA()); } @@ -89,7 +90,7 @@ public void mergeValueLoadsInstanceFromRepository() { public void mergeValueSetsValueDirectlyWhenItIsNull() { final OWLClassD target = new OWLClassD(Generators.createIndividualIdentifier()); target.setOwlClassA(Generators.generateOwlClassAInstance()); - sut.mergeValue(target, new ChangeRecordImpl(refASpec, null), descriptor); + sut.mergeValue(target, new ChangeRecord(refASpec, null), descriptor); assertNull(target.getOwlClassA()); } @@ -99,7 +100,7 @@ public void mergeValueSetsTheMergedValueDirectlyWhenItRepresentsANewInstance() { target.setOwlClassA(Generators.generateOwlClassAInstance()); final OWLClassA merged = Generators.generateOwlClassAInstance(); - sut.mergeValue(target, new ChangeRecordImpl(refASpec, merged), descriptor); + sut.mergeValue(target, new ChangeRecord(refASpec, merged), descriptor); assertSame(merged, target.getOwlClassA()); } @@ -111,7 +112,7 @@ public void mergeSetsTheMergedValueDirectlyWhenItHasNoIdentifier() { merged.setUri(null); when(uow.readObject(any(), isNull(), any())).thenThrow(new NullPointerException()); - sut.mergeValue(target, new ChangeRecordImpl(refASpec, merged), descriptor); + sut.mergeValue(target, new ChangeRecord(refASpec, merged), descriptor); assertSame(merged, target.getOwlClassA()); } @@ -123,7 +124,7 @@ void mergeReplacesNewValueInChangeRecordWhenItIsReadFromUoW() { final OWLClassD target = new OWLClassD(Generators.createIndividualIdentifier()); target.setOwlClassA(orig); when(uow.readObject(OWLClassA.class, merged.getUri(), descriptor)).thenReturn(loaded); - final ChangeRecord changeRecord = new ChangeRecordImpl(refASpec, merged); + final ChangeRecord changeRecord = new ChangeRecord(refASpec, merged); sut.mergeValue(target, changeRecord, descriptor); assertEquals(loaded, target.getOwlClassA()); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/MapValueMergerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/MapValueMergerTest.java index a2fe4392d..427991237 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/MapValueMergerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/merge/MapValueMergerTest.java @@ -22,7 +22,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.sessions.change.ChangeRecordImpl; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.junit.jupiter.MockitoSettings; @@ -61,7 +61,7 @@ public void mergeMergesPropertiesMap() { merged.getProperties().get(newKey).add(vOne); merged.getProperties().get(newKey).add(vTwo); - sut.mergeValue(orig, new ChangeRecordImpl(metamodelMocks.forOwlClassB().propertiesSpec(), merged.getProperties()), descriptor); + sut.mergeValue(orig, new ChangeRecord(metamodelMocks.forOwlClassB().propertiesSpec(), merged.getProperties()), descriptor); assertEquals(merged.getProperties(), orig.getProperties()); } @@ -72,7 +72,7 @@ public void mergeSetsFieldValueToNullWhenMergedInstanceValueIsNull() { final OWLClassB merged = new OWLClassB(orig.getUri()); merged.setProperties(null); - sut.mergeValue(orig, new ChangeRecordImpl(metamodelMocks.forOwlClassB().propertiesSpec(), merged.getProperties()), descriptor); + sut.mergeValue(orig, new ChangeRecord(metamodelMocks.forOwlClassB().propertiesSpec(), merged.getProperties()), descriptor); assertNull(orig.getProperties()); } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/validator/IntegrityConstraintsValidatorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/validator/IntegrityConstraintsValidatorTest.java index f4fdc704a..59670172d 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/validator/IntegrityConstraintsValidatorTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/validator/IntegrityConstraintsValidatorTest.java @@ -17,7 +17,12 @@ */ package cz.cvut.kbss.jopa.sessions.validator; -import cz.cvut.kbss.jopa.environment.*; +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassJ; +import cz.cvut.kbss.jopa.environment.OWLClassL; +import cz.cvut.kbss.jopa.environment.OWLClassN; +import cz.cvut.kbss.jopa.environment.OWLClassQ; +import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.exceptions.CardinalityConstraintViolatedException; import cz.cvut.kbss.jopa.exceptions.IntegrityConstraintViolatedException; @@ -30,10 +35,9 @@ import cz.cvut.kbss.jopa.model.metamodel.Attribute; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.Identifier; -import cz.cvut.kbss.jopa.sessions.ObjectChangeSet; -import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl; +import cz.cvut.kbss.jopa.sessions.change.ChangeRecord; import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory; -import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSetImpl; +import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet; import cz.cvut.kbss.jopa.utils.Configuration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -75,8 +79,8 @@ public void validationOfObjectChangeSetWithValidChangesPasses() throws Exception final OWLClassN clone = createInstanceWithMissingRequiredField(); clone.setStringAttribute("newString"); final OWLClassN orig = createInstanceWithMissingRequiredField(); - final ObjectChangeSet changeSet = new ObjectChangeSetImpl(orig, clone, new EntityDescriptor()); - changeSet.addChangeRecord(new ChangeRecordImpl( + final ObjectChangeSet changeSet = ChangeSetFactory.createObjectChangeSet(orig, clone, new EntityDescriptor()); + changeSet.addChangeRecord(new ChangeRecord( metamodel.entity(OWLClassN.class).getFieldSpecification(OWLClassN.getStringAttributeField().getName()), "newString")); @@ -110,8 +114,8 @@ private OWLClassN createInstanceWithMissingRequiredField() { public void missingRequiredAttributeInChangeSetFailsValidation() throws Exception { final OWLClassN clone = createInstanceWithMissingRequiredField(); final OWLClassN orig = createInstanceWithMissingRequiredField(); - final ObjectChangeSet changeSet = new ObjectChangeSetImpl(orig, clone, new EntityDescriptor()); - changeSet.addChangeRecord(new ChangeRecordImpl( + final ObjectChangeSet changeSet = ChangeSetFactory.createObjectChangeSet(orig, clone, new EntityDescriptor()); + changeSet.addChangeRecord(new ChangeRecord( metamodel.entity(OWLClassN.class).getFieldSpecification(OWLClassN.getStringAttributeField().getName()), null)); @@ -122,7 +126,7 @@ public void missingRequiredAttributeInChangeSetFailsValidation() throws Exceptio public void missingRequiredFieldValueFailsValidation() throws Exception { final OWLClassN n = createInstanceWithMissingRequiredField(); final Attribute att = metamodel.entity(OWLClassN.class) - .getDeclaredAttribute(OWLClassN.getStringAttributeField().getName()); + .getDeclaredAttribute(OWLClassN.getStringAttributeField().getName()); assertThrows(IntegrityConstraintViolatedException.class, () -> validator.validate(n.getId(), att, n.getStringAttribute())); } @@ -149,10 +153,10 @@ public void violatedMaximumCardinalityConstraintFailsValidation() throws Excepti for (int i = 0; i < max + 1; i++) { clone.getReferencedList().add(new OWLClassA()); } - final ObjectChangeSet changeSet = new ObjectChangeSetImpl(orig, clone, new EntityDescriptor()); + final ObjectChangeSet changeSet = ChangeSetFactory.createObjectChangeSet(orig, clone, new EntityDescriptor()); changeSet.addChangeRecord( - new ChangeRecordImpl(metamodel.entity(OWLClassL.class) - .getFieldSpecification(OWLClassL.getReferencedListField().getName()), + new ChangeRecord(metamodel.entity(OWLClassL.class) + .getFieldSpecification(OWLClassL.getReferencedListField().getName()), clone.getReferencedList())); assertThrows(CardinalityConstraintViolatedException.class, () -> validator.validate(changeSet, metamodel)); @@ -182,7 +186,7 @@ public void violatedNonEmptyConstraintOnPluralAttributeFailsValidationOfChangeSe clone.setOwlClassA(Collections.emptySet()); final ObjectChangeSet changeSet = ChangeSetFactory .createObjectChangeSet(original, clone, new EntityDescriptor()); - changeSet.addChangeRecord(new ChangeRecordImpl( + changeSet.addChangeRecord(new ChangeRecord( metamodel.entity(OWLClassJ.class).getFieldSpecification(OWLClassJ.getOwlClassAField().getName()), clone.getOwlClassA())); assertThrows(CardinalityConstraintViolatedException.class, () -> validator.validate(changeSet, metamodel)); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/transactions/EntityTransactionImplTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/transactions/EntityTransactionImplTest.java index a69fe4c7e..4feea81d8 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/transactions/EntityTransactionImplTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/transactions/EntityTransactionImplTest.java @@ -18,26 +18,22 @@ package cz.cvut.kbss.jopa.transactions; import cz.cvut.kbss.jopa.exceptions.RollbackException; -import cz.cvut.kbss.jopa.model.AbstractEntityManager; -import cz.cvut.kbss.jopa.sessions.UnitOfWork; 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 static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) public class EntityTransactionImplTest { @Mock private EntityTransactionWrapper wrapperMock; - @Mock - private AbstractEntityManager emMock; - @Mock - private UnitOfWork uowMock; private EntityTransactionImpl transaction; @@ -53,16 +49,14 @@ public void callingConstructorWithNullArgumentsThrowsNPX() { @Test public void testBegin() { - when(wrapperMock.getEntityManager()).thenReturn(emMock); assertFalse(transaction.isActive()); transaction.begin(); assertTrue(transaction.isActive()); - verify(emMock).transactionStarted(transaction); + verify(wrapperMock).begin(); } @Test public void testBeginAlreadyActive() { - when(wrapperMock.getEntityManager()).thenReturn(emMock); assertFalse(transaction.isActive()); transaction.begin(); assertTrue(transaction.isActive()); @@ -72,22 +66,10 @@ public void testBeginAlreadyActive() { @Test public void testCommit() { - when(wrapperMock.getEntityManager()).thenReturn(emMock); - when(wrapperMock.getTransactionUOW()).thenReturn(uowMock); transaction.begin(); transaction.commit(); assertFalse(transaction.isActive()); - verify(emMock).transactionFinished(transaction); - } - - @Test - public void testCommitAndRelease() { - when(wrapperMock.getEntityManager()).thenReturn(emMock); - when(wrapperMock.getTransactionUOW()).thenReturn(uowMock); - when(uowMock.shouldReleaseAfterCommit()).thenReturn(Boolean.TRUE); - transaction.begin(); - transaction.commit(); - verify(emMock).removeCurrentPersistenceContext(); + verify(wrapperMock).commit(); } @Test @@ -95,23 +77,8 @@ public void testCommitNotActive() { assertThrows(IllegalStateException.class, () -> transaction.commit()); } - @Test - public void testCommitWithException() { - when(wrapperMock.getEntityManager()).thenReturn(emMock); - when(wrapperMock.getTransactionUOW()).thenReturn(uowMock); - doThrow(RuntimeException.class).when(uowMock).commit(); - transaction.begin(); - try { - assertThrows(RuntimeException.class, () -> transaction.commit()); - } finally { - verify(emMock).removeCurrentPersistenceContext(); - } - } - @Test public void testCommitRollbackOnly() { - when(wrapperMock.getEntityManager()).thenReturn(emMock); - when(wrapperMock.getTransactionUOW()).thenReturn(uowMock); transaction.begin(); transaction.setRollbackOnly(); assertThrows(RollbackException.class, () -> transaction.commit()); @@ -119,12 +86,10 @@ public void testCommitRollbackOnly() { @Test public void testRollback() { - when(wrapperMock.getEntityManager()).thenReturn(emMock); - when(wrapperMock.getTransactionUOW()).thenReturn(uowMock); transaction.begin(); transaction.rollback(); assertFalse(transaction.isActive()); - verify(uowMock).rollback(); + verify(wrapperMock).rollback(); } @Test @@ -135,7 +100,6 @@ public void testRollbackNotActive() { @Test public void testSetRollbackOnly() { - when(wrapperMock.getEntityManager()).thenReturn(emMock); transaction.begin(); assertFalse(transaction.isRollbackOnly()); transaction.setRollbackOnly(); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/transactions/EntityTransactionWrapperTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/transactions/EntityTransactionWrapperTest.java new file mode 100644 index 000000000..6aa006f78 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/transactions/EntityTransactionWrapperTest.java @@ -0,0 +1,91 @@ +package cz.cvut.kbss.jopa.transactions; + +import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.exceptions.RollbackException; +import cz.cvut.kbss.jopa.model.AbstractEntityManager; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class EntityTransactionWrapperTest { + + @Mock + private AbstractEntityManager em; + + @Mock + private UnitOfWork uow; + + @InjectMocks + private EntityTransactionWrapper sut; + + @Test + void beginInitializesUoWAndNotifiesEntityManagerOfTransactionStart() { + when(em.getCurrentPersistenceContext()).thenReturn(uow); + final EntityTransaction transaction = sut.getTransaction(); + sut.begin(); + final InOrder inOrder = Mockito.inOrder(em); + inOrder.verify(em).getCurrentPersistenceContext(); + inOrder.verify(em).transactionStarted(transaction); + verify(uow).begin(); + } + + @Test + void commitCommitsTransactionalUoW() { + when(em.getCurrentPersistenceContext()).thenReturn(uow); + sut.getTransaction(); + sut.begin(); + sut.commit(); + verify(uow).commit(); + } + + @Test + void commitRemovesCurrentPersistenceContextFromEntityManagerWhenUoWCommitThrowsException() { + when(em.getCurrentPersistenceContext()).thenReturn(uow); + doThrow(new OWLPersistenceException("Commit exception.")).when(uow).commit(); + sut.getTransaction(); + sut.begin(); + assertThrows(RollbackException.class, () -> sut.commit()); + verify(em).removeCurrentPersistenceContext(); + } + + @Test + void transactionFinishedNotifiesEntityManagerOfTransactionFinish() { + when(em.getCurrentPersistenceContext()).thenReturn(uow); + final EntityTransaction transaction = sut.getTransaction(); + sut.begin(); + sut.commit(); + sut.transactionFinished(); + verify(em).transactionFinished(transaction); + } + + @Test + void rollbackReleasesCurrentUoW() { + when(em.getCurrentPersistenceContext()).thenReturn(uow); + sut.getTransaction(); + sut.begin(); + sut.rollback(); + verify(em).removeCurrentPersistenceContext(); + } + + @Test + void rollbackFollowedByTransactionFinishedDoesNotThrowException() { + when(em.getCurrentPersistenceContext()).thenReturn(uow); + final EntityTransaction transaction = sut.getTransaction(); + sut.begin(); + sut.rollback(); + sut.transactionFinished(); + verify(em).removeCurrentPersistenceContext(); + verify(em).transactionFinished(transaction); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/utils/ChangeTrackingModeTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/utils/ChangeTrackingModeTest.java new file mode 100644 index 000000000..44d90184a --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/utils/ChangeTrackingModeTest.java @@ -0,0 +1,42 @@ +package cz.cvut.kbss.jopa.utils; + +import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Map; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ChangeTrackingModeTest { + + @ParameterizedTest + @MethodSource("configOptions") + void resolveResolvesValueFromConfiguration(ChangeTrackingMode expected, String configValue) { + final Configuration config = new Configuration(Map.of(JOPAPersistenceProperties.CHANGE_TRACKING_MODE, configValue)); + assertEquals(expected, ChangeTrackingMode.resolve(config)); + } + + static Stream configOptions() { + return Stream.of( + Arguments.of(ChangeTrackingMode.IMMEDIATE, "immediate"), + Arguments.of(ChangeTrackingMode.IMMEDIATE, "IMMEDIATE"), + Arguments.of(ChangeTrackingMode.ON_COMMIT, "on_commit"), + Arguments.of(ChangeTrackingMode.ON_COMMIT, "ON_COMMIT") + ); + } + + @Test + void resolveReturnsOnCommitForRdf4jDataSourceIfValueIsNotExplicitlyConfigured() { + final Configuration config = new Configuration(Map.of(JOPAPersistenceProperties.DATA_SOURCE_CLASS, "cz.cvut.kbss.ontodriver.rdf4j.Rdf4jDataSource")); + assertEquals(ChangeTrackingMode.ON_COMMIT, ChangeTrackingMode.resolve(config)); + } + + @Test + void resolveReturnsImmediateByDefault() { + assertEquals(ChangeTrackingMode.IMMEDIATE, ChangeTrackingMode.resolve(new Configuration())); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/utils/JOPALazyUtilsTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/utils/JOPALazyUtilsTest.java new file mode 100644 index 000000000..9af6db2cf --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/utils/JOPALazyUtilsTest.java @@ -0,0 +1,103 @@ +package cz.cvut.kbss.jopa.utils; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassC; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingListProxy; +import cz.cvut.kbss.jopa.proxy.lazy.gen.LazyLoadingEntityProxyGenerator; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +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) +class JOPALazyUtilsTest { + + @Mock + private UnitOfWork uow; + + @Test + void loadTriggersLazyLoadingForProvidedLazyLoadingProxy() { + when(uow.isActive()).thenReturn(true); + final OWLClassC entity = new OWLClassC(Generators.createIndividualIdentifier()); + final FieldSpecification fieldSpec = mock(FieldSpecification.class); + final LazyLoadingListProxy proxy = new LazyLoadingListProxy(entity, fieldSpec, uow); + JOPALazyUtils.load(proxy); + verify(uow).loadEntityField(entity, fieldSpec); + } + + @Test + void loadDoesNothingWhenObjectIsNotLazyLoadingProxy() { + JOPALazyUtils.load(new OWLClassC(Generators.createIndividualIdentifier())); + verify(uow, never()).loadEntityField(any(), any()); + } + + @Test + void loadDoesNothingForNullArgument() { + assertDoesNotThrow(() -> JOPALazyUtils.load(null)); + } + + @Test + void isLoadedReturnsFalseForLazyLoadingProxyInstance() { + final OWLClassC entity = new OWLClassC(Generators.createIndividualIdentifier()); + final FieldSpecification fieldSpec = mock(FieldSpecification.class); + final LazyLoadingListProxy proxy = new LazyLoadingListProxy(entity, fieldSpec, uow); + assertFalse(JOPALazyUtils.isLoaded(proxy)); + } + + @Test + void isLoadedReturnsTrueWhenObjectIsNotLazyLoadingProxy() { + assertTrue(JOPALazyUtils.isLoaded(new ArrayList<>())); + } + + @Test + void isLoadedReturnsTrueForNullArgument() { + assertTrue(JOPALazyUtils.isLoaded(null)); + } + + @Test + void isLoadedReturnsTrueForLazyLoadingProxyInstanceThatIsLoaded() { + final OWLClassC entity = new OWLClassC(Generators.createIndividualIdentifier()); + final List list = List.of(Generators.generateOwlClassAInstance()); + final FieldSpecification fieldSpec = mock(FieldSpecification.class); + final LazyLoadingListProxy proxy = new LazyLoadingListProxy(entity, fieldSpec, uow); + when(uow.isActive()).thenReturn(true); + when(uow.loadEntityField(entity, fieldSpec)).thenReturn(list); + proxy.triggerLazyLoading(); + assertTrue(JOPALazyUtils.isLoaded(proxy)); + } + + @Test + void getClassReturnsEntityClassForLazyLoadingEntityProxy() throws Exception { + final Class proxyCls = new LazyLoadingEntityProxyGenerator().generate(OWLClassA.class); + final OWLClassA proxy = proxyCls.getDeclaredConstructor().newInstance(); + assertEquals(OWLClassA.class, JOPALazyUtils.getClass(proxy)); + } + + @Test + void getClassReturnsObjectClassWhenObjectIsNotLazyLoadingEntityProxy() { + final OWLClassA entity = Generators.generateOwlClassAInstance(); + assertEquals(OWLClassA.class, JOPALazyUtils.getClass(entity)); + } + + @Test + void getClassThrowsNullPointerExceptionForNullArgument() { + assertThrows(NullPointerException.class, () -> JOPALazyUtils.getClass(null)); + } +} diff --git a/jopa-integration-tests-jena/pom.xml b/jopa-integration-tests-jena/pom.xml index 3c8df1914..8d059a483 100644 --- a/jopa-integration-tests-jena/pom.xml +++ b/jopa-integration-tests-jena/pom.xml @@ -5,7 +5,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml 4.0.0 @@ -36,11 +36,6 @@ - - dev.aspectj - aspectj-maven-plugin - - maven-javadoc-plugin diff --git a/jopa-integration-tests-owlapi/pom.xml b/jopa-integration-tests-owlapi/pom.xml index fa2b5ad9b..e762433cd 100644 --- a/jopa-integration-tests-owlapi/pom.xml +++ b/jopa-integration-tests-owlapi/pom.xml @@ -5,7 +5,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml 4.0.0 @@ -79,11 +79,6 @@ - - dev.aspectj - aspectj-maven-plugin - - maven-javadoc-plugin diff --git a/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/integration/owlapi/ListsTest.java b/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/integration/owlapi/ListsTest.java index 9c1da8b1e..91214f614 100644 --- a/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/integration/owlapi/ListsTest.java +++ b/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/integration/owlapi/ListsTest.java @@ -3,6 +3,7 @@ import cz.cvut.kbss.jopa.test.environment.OwlapiDataAccessor; import cz.cvut.kbss.jopa.test.environment.OwlapiPersistenceFactory; import cz.cvut.kbss.jopa.test.runner.ListsTestRunner; +import org.junit.jupiter.api.Disabled; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,4 +14,18 @@ public class ListsTest extends ListsTestRunner { public ListsTest() { super(LOG, new OwlapiPersistenceFactory(), new OwlapiDataAccessor()); } + + // RDF lists are not supported by the OWL API driver + @Disabled + @Override + public void persistSavesRdfCollectionTerminatedByNil() { + // Do nothing + } + + // RDF lists are not supported by the OWL API driver + @Disabled + @Override + public void updateUpdatesRdfCollectionTerminatedByNilAndMaintainsCorrectListEnding() { + // Do nothing + } } diff --git a/jopa-integration-tests-rdf4j/pom.xml b/jopa-integration-tests-rdf4j/pom.xml index de75105c2..986f1825a 100644 --- a/jopa-integration-tests-rdf4j/pom.xml +++ b/jopa-integration-tests-rdf4j/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml jopa-integration-tests-rdf4j @@ -37,11 +37,6 @@ - - dev.aspectj - aspectj-maven-plugin - - maven-javadoc-plugin diff --git a/jopa-integration-tests-rdf4j/src/test/java/cz/cvut/kbss/jopa/test/integration/rdf4j/UpdateOperationsTest.java b/jopa-integration-tests-rdf4j/src/test/java/cz/cvut/kbss/jopa/test/integration/rdf4j/UpdateOperationsTest.java index 0ea708504..b98ff219f 100644 --- a/jopa-integration-tests-rdf4j/src/test/java/cz/cvut/kbss/jopa/test/integration/rdf4j/UpdateOperationsTest.java +++ b/jopa-integration-tests-rdf4j/src/test/java/cz/cvut/kbss/jopa/test/integration/rdf4j/UpdateOperationsTest.java @@ -20,6 +20,7 @@ import cz.cvut.kbss.jopa.test.environment.Rdf4jDataAccessor; import cz.cvut.kbss.jopa.test.environment.Rdf4jPersistenceFactory; import cz.cvut.kbss.jopa.test.runner.UpdateOperationsRunner; +import org.junit.jupiter.api.Disabled; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,4 +31,11 @@ public class UpdateOperationsTest extends UpdateOperationsRunner { public UpdateOperationsTest() { super(LOG, new Rdf4jPersistenceFactory(), new Rdf4jDataAccessor()); } + + // TODO Re-enable after new transactional mechanism is implemented + @Disabled + @Override + public void concurrentTransactionsLeaveDataInConsistentState() { + super.concurrentTransactionsLeaveDataInConsistentState(); + } } diff --git a/jopa-integration-tests-rdf4j/src/test/java/cz/cvut/kbss/jopa/test/query/rdf4j/SoqlTest.java b/jopa-integration-tests-rdf4j/src/test/java/cz/cvut/kbss/jopa/test/query/rdf4j/SoqlTest.java index 75341482d..acd01ba42 100644 --- a/jopa-integration-tests-rdf4j/src/test/java/cz/cvut/kbss/jopa/test/query/rdf4j/SoqlTest.java +++ b/jopa-integration-tests-rdf4j/src/test/java/cz/cvut/kbss/jopa/test/query/rdf4j/SoqlTest.java @@ -45,7 +45,7 @@ public class SoqlTest extends SoqlRunner { static void setUpBeforeClass() { final Rdf4jPersistenceFactory persistenceFactory = new Rdf4jPersistenceFactory(); em = persistenceFactory.getEntityManager("SOQLTests", false, - Collections.singletonMap(Rdf4jOntoDriverProperties.USE_INFERENCE, "true")); + Collections.singletonMap(Rdf4jOntoDriverProperties.USE_INFERENCE, "false")); QueryTestEnvironment.generateTestData(em); em.clear(); em.getEntityManagerFactory().getCache().evictAll(); diff --git a/jopa-integration-tests/pom.xml b/jopa-integration-tests/pom.xml index b670aadd7..b38733e3a 100644 --- a/jopa-integration-tests/pom.xml +++ b/jopa-integration-tests/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml 4.0.0 @@ -35,11 +35,6 @@ - - dev.aspectj - aspectj-maven-plugin - - maven-javadoc-plugin diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLChildClassA.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLChildClassA.java index 69061fb44..47747a102 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLChildClassA.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLChildClassA.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.test; import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.jopa.vocabulary.DC; import java.net.URI; import java.util.Set; @@ -31,7 +32,7 @@ public class OWLChildClassA implements OWLParentA, OWLParentB { @Types(fetchType = FetchType.EAGER) private Set types; - @OWLAnnotationProperty(iri = Vocabulary.DC_SOURCE) + @OWLAnnotationProperty(iri = DC.Terms.SOURCE) private Set pluralAnnotationProperty; @OWLDataProperty(iri = Vocabulary.P_E_STRING_ATTRIBUTE) diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassA.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassA.java index c72959a2e..c333d4171 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassA.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassA.java @@ -19,6 +19,7 @@ import cz.cvut.kbss.jopa.model.annotations.ConstructorResult; import cz.cvut.kbss.jopa.model.annotations.EntityResult; +import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.FieldResult; import cz.cvut.kbss.jopa.model.annotations.Id; import cz.cvut.kbss.jopa.model.annotations.NamedNativeQueries; @@ -31,6 +32,7 @@ import cz.cvut.kbss.jopa.model.annotations.VariableResult; import java.net.URI; +import java.util.HashSet; import java.util.Objects; import java.util.Set; @@ -60,8 +62,8 @@ public class OWLClassA implements HasUri { public static final String CONSTRUCTOR_MAPPING = "OWLClassA.constructorMapping"; public static final String ENTITY_MAPPING = "OWLClassA.entityMapping"; - @Types - private Set types; + @Types(fetchType = FetchType.EAGER) + private Set types = new HashSet<>(); @Id private URI uri; @@ -111,10 +113,9 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (!(o instanceof OWLClassA)) { + if (!(o instanceof OWLClassA owlClassA)) { return false; } - OWLClassA owlClassA = (OWLClassA) o; return Objects.equals(getTypes(), owlClassA.getTypes()) && Objects.equals(getUri(), owlClassA.getUri()) && Objects.equals(getStringAttribute(), owlClassA.getStringAttribute()); } @@ -127,9 +128,7 @@ public int hashCode() { public String toString() { String out = "OWLClassA: uri = " + uri; out += ", stringAttribute = " + stringAttribute; - if (types != null) { - out += ", types = {" + types + "}"; - } + out += ", types = {" + types + "}"; return out; } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassC.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassC.java index 972369166..b86cf50cf 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassC.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassC.java @@ -36,6 +36,10 @@ public class OWLClassC implements HasUri { @OWLObjectProperty(iri = Vocabulary.P_HAS_SIMPLE_LIST) private List simpleList; + @RDFCollection + @OWLObjectProperty(iri = Vocabulary.P_HAS_RDF_COLLECTION) + private List rdfCollection; + public OWLClassC() { } @@ -68,6 +72,14 @@ public List getSimpleList() { return simpleList; } + public List getRdfCollection() { + return rdfCollection; + } + + public void setRdfCollection(List rdfCollection) { + this.rdfCollection = rdfCollection; + } + @Override public String toString() { String out = "OWLClassC: uri = " + uri; @@ -77,6 +89,9 @@ public String toString() { if (simpleList != null) { out += ", simpleList = {" + simpleList + "}"; } + if (rdfCollection != null) { + out += ", rdfCollection = {" + rdfCollection + "}"; + } return out; } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassF.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassF.java index 46b7729fa..818bf8792 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassF.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassF.java @@ -17,10 +17,13 @@ */ package cz.cvut.kbss.jopa.test; -import cz.cvut.kbss.jopa.model.annotations.*; +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.HashSet; import java.util.Set; @OWLClass(iri = Vocabulary.C_OWL_CLASS_F) @@ -61,9 +64,6 @@ public void setSecondStringAttribute(String secondStringAttribute) { } public Set getSimpleSet() { - if (simpleSet == null) { - this.simpleSet = new HashSet<>(); - } return simpleSet; } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassL.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassL.java index 13e4d1487..0f5b10a26 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassL.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassL.java @@ -30,7 +30,7 @@ public class OWLClassL implements HasUri { private URI uri; @Sequence(type = SequenceType.simple) - @OWLObjectProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#C-hasSimpleSequence") + @OWLObjectProperty(iri = Vocabulary.p_l_simpleListAttribute, fetch = FetchType.LAZY) @ParticipationConstraints({ @ParticipationConstraint(min = 1, owlObjectIRI = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassA") @@ -38,21 +38,21 @@ public class OWLClassL implements HasUri { private List simpleList; @Sequence(type = SequenceType.referenced) - @OWLObjectProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#C-hasReferencedSequence") + @OWLObjectProperty(iri = Vocabulary.p_l_referencedListAttribute, fetch = FetchType.LAZY) @ParticipationConstraints({ @ParticipationConstraint(max = 2, owlObjectIRI = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassA") }) private List referencedList; - @OWLObjectProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#hasA") + @OWLObjectProperty(iri = Vocabulary.p_l_aSetAttribute, fetch = FetchType.LAZY) @ParticipationConstraints({ @ParticipationConstraint(min = 1, max = 5, owlObjectIRI = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassA") }) private Set set; - @OWLObjectProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#hasAExtra") + @OWLObjectProperty(iri = Vocabulary.p_l_singleOwlClassAAttribute, fetch = FetchType.LAZY) @ParticipationConstraints({ @ParticipationConstraint(min = 1, owlObjectIRI = "http://krizik.felk.cvut.cz/ontologies/jopa/entities#OWLClassA") diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassN.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassN.java index e13a7381b..292c952e4 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassN.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassN.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.test; import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.jopa.vocabulary.DC; import java.net.URI; import java.util.Map; @@ -39,7 +40,7 @@ public class OWLClassN { @OWLDataProperty(iri = Vocabulary.P_N_STRING_ATTRIBUTE) private String stringAttribute; - @OWLAnnotationProperty(iri = Vocabulary.DC_SOURCE) + @OWLAnnotationProperty(iri = DC.Terms.SOURCE) private Set pluralAnnotationProperty; @Inferred diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassS.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassS.java index 0d059f5a4..545aff9c9 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassS.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassS.java @@ -21,6 +21,7 @@ import cz.cvut.kbss.jopa.model.annotations.OWLClass; import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; import cz.cvut.kbss.jopa.model.annotations.Types; +import cz.cvut.kbss.jopa.vocabulary.DC; import cz.cvut.kbss.jopa.vocabulary.RDFS; import java.util.Set; @@ -31,7 +32,7 @@ public class OWLClassS extends OWLClassSParent { @OWLAnnotationProperty(iri = RDFS.LABEL) private String name; - @OWLDataProperty(iri = Vocabulary.DC_DESCRIPTION) + @OWLDataProperty(iri = DC.Terms.DESCRIPTION) private String description; @Types diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassV.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassV.java index 1b9b0ef1b..69759dea5 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassV.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassV.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.test; import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.jopa.vocabulary.DC; import cz.cvut.kbss.jopa.vocabulary.RDFS; import java.util.Map; @@ -32,7 +33,7 @@ public class OWLClassV { @OWLAnnotationProperty(iri = RDFS.LABEL) private String name; - @OWLAnnotationProperty(iri = Vocabulary.DC_DESCRIPTION) + @OWLAnnotationProperty(iri = DC.Terms.DESCRIPTION) private String description; @Types diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/Thing.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/Thing.java index e398d638f..df1ccba80 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/Thing.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/Thing.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.test; import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.jopa.vocabulary.DC; import cz.cvut.kbss.jopa.vocabulary.OWL; import cz.cvut.kbss.jopa.vocabulary.RDFS; @@ -33,7 +34,7 @@ public class Thing { @OWLAnnotationProperty(iri = RDFS.LABEL) private String name; - @OWLAnnotationProperty(iri = Vocabulary.DC_DESCRIPTION) + @OWLAnnotationProperty(iri = DC.Terms.DESCRIPTION) private String description; @Types diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/Vocabulary.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/Vocabulary.java index b7c15dcab..1043e020d 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/Vocabulary.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/Vocabulary.java @@ -76,6 +76,11 @@ public class Vocabulary { public static final String p_e_simpleList = ATTRIBUTE_IRI_BASE + "simpleList"; public static final String p_e_enumeratedOrdinalColor = ATTRIBUTE_IRI_BASE + "enumeratedOrdinalColor"; + public static final String p_l_simpleListAttribute = ATTRIBUTE_IRI_BASE + "l-hasSimpleSequence"; + public static final String p_l_referencedListAttribute = ATTRIBUTE_IRI_BASE + "l-hasReferencedSequence"; + public static final String p_l_aSetAttribute = ATTRIBUTE_IRI_BASE + "l-hasA"; + public static final String p_l_singleOwlClassAAttribute = ATTRIBUTE_IRI_BASE + "has-singleA"; + public static final String p_m_booleanAttribute = ATTRIBUTE_IRI_BASE + "m-booleanAttribute"; public static final String p_m_intAttribute = ATTRIBUTE_IRI_BASE + "m-intAttribute"; public static final String p_m_longAttribute = ATTRIBUTE_IRI_BASE + "m-longAttribute"; @@ -122,13 +127,10 @@ public class Vocabulary { public static final String V_HAS_THING = ATTRIBUTE_IRI_BASE + "hasThing"; - public static final String DC_DESCRIPTION = "http://purl.org/dc/terms/description"; - public static final String DC_SOURCE = "http://purl.org/dc/terms/source"; + public static final String P_HAS_SIMPLE_LIST = ATTRIBUTE_IRI_BASE + "hasSimpleSequence"; + public static final String P_HAS_REFERENCED_LIST = ATTRIBUTE_IRI_BASE + "hasReferencedSequence"; - public static final String P_HAS_SIMPLE_LIST = - "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#C-hasSimpleSequence"; - public static final String P_HAS_REFERENCED_LIST = - "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#C-hasReferencedSequence"; + public static final String P_HAS_RDF_COLLECTION = ATTRIBUTE_IRI_BASE + "hasRdfCollection"; public static final String P_X_LOCAL_DATE_ATTRIBUTE = ATTRIBUTE_IRI_BASE + "xLocalDate"; public static final String P_X_LOCAL_DATETIME_ATTRIBUTE = ATTRIBUTE_IRI_BASE + "xLocalDateTime"; diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/Generators.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/Generators.java index a32d1176c..c38be057e 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/Generators.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/Generators.java @@ -82,6 +82,14 @@ public static List createDataPropertyList() { return list; } + public static List createRDFCollection(int size) { + assert size > 0; + final List lst = new ArrayList<>(size); + generateInstances(lst, + "http://krizik.felk.cvut.cz/ontologies/jopa/tests/rdf-collection-item", size); + return lst; + } + public static List createListOfIdentifiers() { return createSimpleList().stream().map(OWLClassA::getUri).collect(Collectors.toList()); } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/CriteriaRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/CriteriaRunner.java index b691126f4..755daf9f9 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/CriteriaRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/CriteriaRunner.java @@ -22,7 +22,7 @@ import cz.cvut.kbss.jopa.model.query.criteria.ParameterExpression; import cz.cvut.kbss.jopa.model.query.criteria.Predicate; import cz.cvut.kbss.jopa.model.query.criteria.Root; -import cz.cvut.kbss.jopa.sessions.CriteriaBuilder; +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; import cz.cvut.kbss.jopa.test.OWLClassA; import cz.cvut.kbss.jopa.test.OWLClassA_; import cz.cvut.kbss.jopa.test.OWLClassD; diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/QueryRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/QueryRunner.java index 548c548f5..a42d7564e 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/QueryRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/QueryRunner.java @@ -29,6 +29,7 @@ import cz.cvut.kbss.jopa.test.environment.Quad; import cz.cvut.kbss.jopa.test.environment.TestEnvironment; import cz.cvut.kbss.jopa.test.query.QueryTestEnvironment; +import cz.cvut.kbss.jopa.vocabulary.DC; import cz.cvut.kbss.jopa.vocabulary.RDFS; import cz.cvut.kbss.ontodriver.model.LangString; import org.junit.jupiter.api.Test; @@ -92,7 +93,7 @@ void testSelectByDataProperty() { assertFalse(res.isEmpty()); assertEquals(exp.size(), res.size()); for (Object lst2 : res) { - assertTrue(lst2 instanceof LangString); + assertInstanceOf(LangString.class, lst2); // False means we got the expected value assertFalse(exp.add(((LangString) lst2).getValue())); } @@ -244,7 +245,7 @@ void queryWithMappingReturnsResultWithVariablesMappedAccordingly() { new LangString(a.getStringAttribute(), "en"))); assertEquals(expected.size(), res.size()); for (Object row : res) { - assertTrue(row instanceof Object[]); + assertInstanceOf(Object[].class, row); final Object[] elems = (Object[]) row; assertEquals(2, elems.length); assertTrue(expected.containsKey(elems[0])); @@ -267,7 +268,7 @@ void queryWithConstructorMappingReturnsCorrectInstances() { private void verifyOWLClassAInstances(List res, Map expected) { for (Object item : res) { - assertTrue(item instanceof OWLClassA); + assertInstanceOf(OWLClassA.class, item); final OWLClassA a = (OWLClassA) item; assertTrue(expected.containsKey(a.getUri())); assertEquals(expected.get(a.getUri()).getStringAttribute(), a.getStringAttribute()); @@ -302,7 +303,7 @@ void queryWithEntityMappingLoadsReferencedEntitiesAsWell() { assertEquals(expected.size(), res.size()); for (Object row : res) { - assertTrue(row instanceof OWLClassD); + assertInstanceOf(OWLClassD.class, row); final OWLClassD inst = (OWLClassD) row; assertTrue(expected.containsKey(inst.getUri())); assertNotNull(inst.getOwlClassA()); @@ -336,7 +337,7 @@ public void queryWithEntityMappingLoadsReferencedEntityAndInheritedAttributes() .setParameter("type", URI.create(Vocabulary.C_OWL_CLASS_T)) .setParameter("hasA", URI.create(Vocabulary.P_HAS_OWL_CLASS_A)) .setParameter("rdfsLabel", URI.create(RDFS.LABEL)) - .setParameter("hasDescription", URI.create(Vocabulary.DC_DESCRIPTION)) + .setParameter("hasDescription", URI.create(DC.Terms.DESCRIPTION)) .setParameter("hasInt", URI.create(Vocabulary.P_T_INTEGER_ATTRIBUTE)) .getResultList(); final Map expected = new HashMap<>(); @@ -344,7 +345,7 @@ public void queryWithEntityMappingLoadsReferencedEntityAndInheritedAttributes() assertEquals(expected.size(), res.size()); for (Object row : res) { - assertTrue(row instanceof OWLClassT); + assertInstanceOf(OWLClassT.class, row); final OWLClassT tActual = (OWLClassT) row; assertTrue(expected.containsKey(tActual.getUri())); final OWLClassT tExpected = expected.get(tActual.getUri()); diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/SoqlRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/SoqlRunner.java index 05104fe7d..44f1d736a 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/SoqlRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/SoqlRunner.java @@ -238,8 +238,7 @@ public void testSelectByLikeWithUppercase() { public void testSelectByAbsoluteValueOfAnInteger() { final List instances = QueryTestEnvironment.getData(OWLClassM.class); final int value = Math.abs(Generators.randomInt()); - final List matching = instances.stream().filter(m -> Math.abs(m.getIntAttribute()) <= value).collect( - Collectors.toList()); + final List matching = instances.stream().filter(m -> Math.abs(m.getIntAttribute()) <= value).toList(); final List result = getEntityManager().createQuery("SELECT m FROM OWLClassM m WHERE ABS(m.intAttribute) <= :value", OWLClassM.class) .setParameter("value", value) .getResultList(); 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 61ee352b3..630770095 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 @@ -67,6 +67,7 @@ void testFindAll() { found = true; assertNotNull(dd.getOwlClassA()); assertEquals(d.getOwlClassA().getUri(), dd.getOwlClassA().getUri()); + assertNotNull(dd.getOwlClassA().getStringAttribute()); break; } } @@ -102,8 +103,7 @@ void testSelectByObjectProperty() { URI.create(Vocabulary.P_HAS_OWL_CLASS_A)) .setParameter("y", a.getUri()); - final List expected = ds.stream().filter(d -> d.getOwlClassA().getUri().equals(a.getUri())).collect( - Collectors.toList()); + final List expected = ds.stream().filter(d -> d.getOwlClassA().getUri().equals(a.getUri())).toList(); final List res = q.getResultList(); assertEquals(expected.size(), res.size()); } @@ -232,7 +232,7 @@ void askQueryWithPositionParameter() { @Test void testCreateTypedNamedNativeQuery() { final List expected = QueryTestEnvironment.getData(OWLClassA.class); - final List uris = expected.stream().map(OWLClassA::getUri).collect(Collectors.toList()); + final List uris = expected.stream().map(OWLClassA::getUri).toList(); final List res = getEntityManager().createNamedQuery("OWLClassA.findAll", OWLClassA.class) .getResultList(); assertEquals(expected.size(), res.size()); @@ -300,7 +300,7 @@ void selectionByObjectPropertySupportsEntityAsQueryParameter() { final List expected = ds.stream().filter(d -> d.getOwlClassA().getUri().equals(a.getUri())) .sorted(Comparator.comparing(OWLClassD::getUri)) - .collect(Collectors.toList()); + .toList(); final List res = q.getResultList(); res.sort(Comparator.comparing(OWLClassD::getUri)); assertEquals(expected.size(), res.size()); @@ -315,11 +315,11 @@ protected void querySupportsCollectionParameters() { final String query = "SELECT ?x WHERE { ?x a ?type . FILTER (?x IN (?values)) }"; final List as = QueryTestEnvironment.getData(OWLClassA.class).stream() .filter(a -> Generators.randomBoolean()) - .collect(Collectors.toList()); + .toList(); final TypedQuery q = getEntityManager().createNativeQuery(query, OWLClassA.class) .setParameter("type", URI.create(Vocabulary.C_OWL_CLASS_A)) .setParameter("values", as.stream().map(OWLClassA::getUri) - .collect(Collectors.toList())); + .toList()); final List result = q.getResultList(); assertEquals(as.size(), result.size()); for (OWLClassA exp : as) { @@ -336,14 +336,14 @@ protected void querySupportsSelectionByDate() { // Now minus i * hour m.setDateAttribute(new Date(now - i * 60 * 60L)); return m; - }).collect(Collectors.toList()); + }).toList(); getEntityManager().getTransaction().begin(); mInstances.forEach(getEntityManager()::persist); getEntityManager().getTransaction().commit(); final LocalDateTime param = LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS).minusHours(3); final List matching = mInstances.stream() .filter(m -> m.getDateAttribute().toInstant().atOffset(ZoneOffset.UTC).isBefore(param.atOffset(ZoneOffset.UTC))) - .collect(Collectors.toList()); + .toList(); try { final List result = getEntityManager().createQuery("SELECT m FROM OWLClassM m WHERE m.dateAttribute < :date", OWLClassM.class) .setParameter("date", param).getResultList(); diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/BaseRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/BaseRunner.java index a84451dea..cb196ebf0 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/BaseRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/BaseRunner.java @@ -252,6 +252,9 @@ void persistCWithLists(OWLClassC instance) { if (instance.getReferencedList() != null) { instance.getReferencedList().forEach(em::persist); } + if (instance.getRdfCollection() != null) { + instance.getRdfCollection().forEach(em::persist); + } }); } @@ -260,7 +263,7 @@ void persistCWithLists(OWLClassC instance) { * * @return The found instance */ - T findRequired(Class type, Object identifier) { + protected T findRequired(Class type, Object identifier) { final T result = em.find(type, identifier); assertNotNull(result); return result; diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/CreateOperationsRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/CreateOperationsRunner.java index 824e663d2..c31f623ab 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/CreateOperationsRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/CreateOperationsRunner.java @@ -196,7 +196,7 @@ void testPersistWithEmptyProperties() { final OWLClassB b = findRequired(OWLClassB.class, entityB.getUri()); assertEquals(entityB.getUri(), b.getUri()); assertEquals(entityB.getStringAttribute(), b.getStringAttribute()); - assertNull(b.getProperties()); + assertTrue(b.getProperties().isEmpty()); } @Test diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/DeleteOperationsRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/DeleteOperationsRunner.java index 5a1200595..44618d5f3 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/DeleteOperationsRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/DeleteOperationsRunner.java @@ -27,6 +27,7 @@ import cz.cvut.kbss.jopa.test.environment.PersistenceFactory; import cz.cvut.kbss.jopa.test.environment.TestEnvironmentUtils; import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; +import cz.cvut.kbss.jopa.vocabulary.DC; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -37,6 +38,8 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.junit.jupiter.api.Assertions.*; public abstract class DeleteOperationsRunner extends BaseRunner { @@ -309,7 +312,7 @@ void testRemoveAllValuesOfPluralPlainIdentifierObjectProperty() { em.getTransaction().commit(); final OWLClassP res = findRequired(OWLClassP.class, entityP.getUri()); - assertNull(res.getIndividuals()); + assertThat(res.getIndividuals(), empty()); } @Test @@ -356,7 +359,7 @@ void testClearUriTypes() { em.getTransaction().commit(); final OWLClassP result = findRequired(OWLClassP.class, entityP.getUri()); - assertNull(result.getTypes()); + assertTrue(result.getTypes().isEmpty()); } @Test @@ -465,7 +468,7 @@ void removeClearsPluralAnnotationPropertyValues() { em.getTransaction().commit(); assertTrue(em.createNativeQuery("SELECT * WHERE { ?x ?hasSource ?source . }") - .setParameter("hasSource", URI.create(Vocabulary.DC_SOURCE)).getResultList().isEmpty()); + .setParameter("hasSource", URI.create(DC.Terms.SOURCE)).getResultList().isEmpty()); } @Test diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/ListsTestRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/ListsTestRunner.java index 6f851059f..ae74cefe4 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/ListsTestRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/ListsTestRunner.java @@ -27,6 +27,8 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -295,8 +297,8 @@ void removingListItemsFromNewlyPersistedOwnerRemovesThemFromPendingReferencesAnd final OWLClassC result = em.find(OWLClassC.class, entityC.getUri()); assertNotNull(result); - assertNull(result.getSimpleList()); - assertNull(result.getReferencedList()); + assertThat(result.getSimpleList(), empty()); + assertThat(result.getReferencedList(), empty()); } @Test @@ -692,4 +694,51 @@ void removeMultilingualReferencedListItemRemovesStatementsFromRepository() { .setParameter("hasContent", URI.create(Vocabulary.p_m_multilingualReferencedList)) .setParameter("value", val, lang).getSingleResult())); } + + @Test + public void persistSavesRdfCollectionTerminatedByNil() { + this.em = getEntityManager("persistSavesRdfCollectionTerminatedByNil", false); + entityC.setRdfCollection(Generators.createRDFCollection(5)); + persistCWithLists(entityC); + + final OWLClassC result = findRequired(OWLClassC.class, entityC.getUri()); + assertEquals(entityC.getRdfCollection(), result.getRdfCollection()); + assertTrue(em.createNativeQuery("ASK { ?node ?hasNext ?nil . }", Boolean.class) + .setParameter("hasNext", URI.create(RDF.REST)) + .setParameter("nil", URI.create(RDF.NIL)).getSingleResult()); + } + + @Test + public void updateUpdatesRdfCollectionTerminatedByNilAndMaintainsCorrectListEnding() { + this.em = getEntityManager("updateUpdatesRdfCollectionTerminatedByNilAndMaintainsCorrectListEnding", false); + entityC.setRdfCollection(Generators.createRDFCollection(5)); + persistCWithLists(entityC); + + final List expectedList = new ArrayList<>(entityC.getRdfCollection()); + entityC.getRdfCollection().remove(entityC.getRdfCollection().size() - 1); + expectedList.remove(expectedList.size() - 1); + + transactional(() -> em.merge(entityC)); + final OWLClassC midUpdate = findRequired(OWLClassC.class, entityC.getUri()); + assertEquals(expectedList, midUpdate.getRdfCollection()); + assertEquals(1, em.createNativeQuery("SELECT (COUNT(?node) as ?cnt) WHERE { ?node ?hasNext ?nil . }", Integer.class) + .setParameter("hasNext", URI.create(RDF.REST)) + .setParameter("nil", URI.create(RDF.NIL)).getSingleResult()); + em.clear(); + + final OWLClassA added = new OWLClassA(Generators.generateUri()); + midUpdate.getRdfCollection().add(added); + expectedList.add(added); + transactional(() -> { + em.merge(midUpdate); + em.persist(added); + }); + final OWLClassC result = findRequired(OWLClassC.class, entityC.getUri()); + // Trigger lazy loading + assertFalse(result.getRdfCollection().isEmpty()); + assertEquals(expectedList, result.getRdfCollection()); + assertEquals(1, em.createNativeQuery("SELECT (COUNT(?node) as ?cnt) WHERE { ?node ?hasNext ?nil . }", Integer.class) + .setParameter("hasNext", URI.create(RDF.REST)) + .setParameter("nil", URI.create(RDF.NIL)).getSingleResult()); + } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/MultipleInheritanceTestRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/MultipleInheritanceTestRunner.java index 05bea99ab..ccaebee42 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/MultipleInheritanceTestRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/MultipleInheritanceTestRunner.java @@ -60,9 +60,8 @@ void entityCanBeFoundByBothParentTypes() { child.setId(id); child.setStringAttribute("AttRVal"); child.setPluralAnnotationProperty(Collections.singleton("seeet")); + persist(child); - em.persist(child); - em.clear(); final OWLChildClassA found = findRequired(OWLChildClassA.class, id); em.clear(); final OWLParentB parentBFound = findRequired(OWLParentB.class, id); @@ -80,8 +79,7 @@ void annotatedMethodPassesDownAnnotationValuesFromSingleParent() { classWithUnProperties.setName("NAME_VALUE"); classWithUnProperties.setTitles(Collections.singleton("title")); - em.persist(classWithUnProperties); - em.clear(); + persist(classWithUnProperties); OWLClassWithUnProperties found = em.find(OWLClassWithUnProperties.class, classWithUnProperties.getId()); @@ -104,8 +102,8 @@ void annotatedMethodPassesDownAnnotationValuesFromMultipleParents() { child.setId(id); child.setAttributeA("Value"); child.setAttributeB(Boolean.FALSE); - em.persist(child); - em.clear(); + persist(child); + final OWLChildClassB found = findRequired(OWLChildClassB.class, id); em.clear(); final OWLInterfaceA parentAFound = findRequired(OWLInterfaceA.class, id); @@ -131,9 +129,8 @@ void annotationInheritedThroughTwoWaysIsHandledProperly() { child.setId(id); child.setName("Name"); child.setAttributeB(Generators.randomBoolean()); + persist(child); - em.persist(child); - em.clear(); final OWLChildClassC found = findRequired(OWLChildClassC.class, id); em.clear(); final OWLInterfaceAnMethods commonParentFound = findRequired(OWLInterfaceAnMethods.class, id); @@ -156,11 +153,7 @@ void mappedSuperClassSupportsAnnotatedMethods() { childOfMappedSuperClass.setUri(uri); childOfMappedSuperClass.setLabel(label); - em.getTransaction().begin(); - - em.persist(childOfMappedSuperClass); - - em.getTransaction().commit(); + persist(childOfMappedSuperClass); verifyExists(ChildOfMappedSuperClass.class, uri); diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsMultiContextRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsMultiContextRunner.java index 4cfee3560..6ed8ee2e8 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsMultiContextRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsMultiContextRunner.java @@ -21,18 +21,32 @@ import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.descriptors.ObjectPropertyCollectionDescriptor; import cz.cvut.kbss.jopa.model.metamodel.ListAttribute; -import cz.cvut.kbss.jopa.test.*; -import cz.cvut.kbss.jopa.test.environment.*; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; +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.OWLClassF; +import cz.cvut.kbss.jopa.test.OWLClassI; +import cz.cvut.kbss.jopa.test.OWLClassM; +import cz.cvut.kbss.jopa.test.Vocabulary; +import cz.cvut.kbss.jopa.test.environment.DataAccessor; +import cz.cvut.kbss.jopa.test.environment.Generators; +import cz.cvut.kbss.jopa.test.environment.PersistenceFactory; +import cz.cvut.kbss.jopa.test.environment.Quad; +import cz.cvut.kbss.jopa.test.environment.TestEnvironmentUtils; import cz.cvut.kbss.jopa.vocabulary.RDF; import org.junit.jupiter.api.Test; import org.slf4j.Logger; -import java.lang.reflect.Field; import java.net.URI; import java.util.Arrays; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; public abstract class RetrieveOperationsMultiContextRunner extends BaseRunner { @@ -115,7 +129,7 @@ void testRetrieveReferencedListFromContext() { } @Test - void testRetrieveLazyReferenceFromContext() throws Exception { + void testRetrieveLazyReferenceFromContext() { this.em = getEntityManager("MultiRetrieveLazyReferenceFromContext", false); final Descriptor iDescriptor = new EntityDescriptor(CONTEXT_ONE, false); final Descriptor aDescriptor = new EntityDescriptor(CONTEXT_TWO); @@ -130,10 +144,9 @@ void testRetrieveLazyReferenceFromContext() throws Exception { em.getTransaction().commit(); final OWLClassI resI = findRequired(OWLClassI.class, entityI.getUri(), iDescriptor); - final Field refAField = OWLClassI.class.getDeclaredField("owlClassA"); - refAField.setAccessible(true); - assertNull(refAField.get(resI)); - assertNotNull(resI.getOwlClassA()); + assertInstanceOf(LazyLoadingProxy.class, resI.getOwlClassA()); + // Trigger lazy loading + assertNotNull(resI.getOwlClassA().getUri()); final OWLClassA resA = findRequired(OWLClassA.class, entityA.getUri(), aDescriptor); // If we were using cache, ref.getOwlClassA() and resA would be the same assertEquals(resI.getOwlClassA().getStringAttribute(), resA.getStringAttribute()); diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsRunner.java index 443437fcf..6e225aa26 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsRunner.java @@ -21,8 +21,10 @@ import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.query.TypedQuery; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; 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.OWLClassG; @@ -44,6 +46,7 @@ import cz.cvut.kbss.jopa.vocabulary.RDF; import cz.cvut.kbss.ontodriver.ReloadableDataSource; import cz.cvut.kbss.ontodriver.config.OntoDriverProperties; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -51,6 +54,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.URI; import java.net.URL; import java.nio.file.Files; @@ -67,9 +71,12 @@ import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -98,18 +105,14 @@ void testRetrieveSimple() { } @Test - void testRetrieveWithLazyAttribute() throws Exception { + void testRetrieveWithLazyAttribute() { this.em = getEntityManager("RetrieveLazy", false); persist(entityI); final OWLClassI resI = findRequired(OWLClassI.class, entityI.getUri()); - final Field f = OWLClassI.class.getDeclaredField("owlClassA"); - f.setAccessible(true); - Object value = f.get(resI); - assertNull(value); - assertNotNull(resI.getOwlClassA()); - value = f.get(resI); - assertNotNull(value); + assertInstanceOf(LazyLoadingProxy.class, resI.getOwlClassA()); + // Trigger lazy loading + assertNotNull(resI.getOwlClassA().getUri()); assertEquals(entityA.getUri(), resI.getOwlClassA().getUri()); assertTrue(em.contains(resI.getOwlClassA())); } @@ -190,7 +193,7 @@ void testRefreshInstanceWithUnmappedProperties() { final OWLClassP p = findRequired(OWLClassP.class, entityP.getUri()); p.getProperties().put(URI.create("http://krizik.felk.cvut.cz/ontologies/jopa#addedProperty"), - Collections.singleton("Test")); + Collections.singleton("Test")); assertNotEquals(properties, p.getProperties()); em.refresh(p); assertEquals(properties, p.getProperties()); @@ -251,14 +254,14 @@ void retrieveLoadsUnmappedPropertiesTogetherWithObjectPropertyValues() { @Test void retrieveGetsStringAttributeWithCorrectLanguageWhenItIsSpecifiedInDescriptor() throws Exception { this.em = getEntityManager("retrieveGetsStringAttributeWithCorrectLanguageWhenItIsSpecifiedInDescriptor", - false); + false); persist(entityA); final String value = "v cestine"; final String lang = "cs"; persistTestData(Collections - .singleton( - new Quad(entityA.getUri(), URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), value, - lang)), em); + .singleton( + new Quad(entityA.getUri(), URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), value, + lang)), em); final Descriptor descriptor = new EntityDescriptor(); descriptor.setLanguage(lang); @@ -279,7 +282,7 @@ void retrieveGetsStringAttributesWithDifferentLanguageTagsSpecifiedInDescriptor( final String csString = "retezec cesky"; final Set testData = new HashSet<>(); testData.add(new Quad(URI.create(entityN.getId()), URI.create(Vocabulary.P_N_STR_ANNOTATION_PROPERTY), - csAnnotation, "cs")); + csAnnotation, "cs")); testData.add( new Quad(URI.create(entityN.getId()), URI.create(Vocabulary.P_N_STRING_ATTRIBUTE), csString, "cs")); persistTestData(testData, em); @@ -289,7 +292,7 @@ void retrieveGetsStringAttributesWithDifferentLanguageTagsSpecifiedInDescriptor( em.getMetamodel().entity(OWLClassN.class).getDeclaredAttribute("annotationProperty"), "en"); descriptor .setAttributeLanguage(em.getMetamodel().entity(OWLClassN.class).getDeclaredAttribute("stringAttribute"), - "cs"); + "cs"); final OWLClassN result = em.find(OWLClassN.class, entityN.getId(), descriptor); assertEquals(entityN.getAnnotationProperty(), result.getAnnotationProperty()); assertEquals(csString, result.getStringAttribute()); @@ -303,9 +306,9 @@ void retrieveAllowsToOverridePULevelLanguageSpecification() throws Exception { persist(entityA); final String value = "cestina"; persistTestData(Collections - .singleton( - new Quad(entityA.getUri(), URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), value, - "cs")), em); + .singleton( + new Quad(entityA.getUri(), URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), value, + "cs")), em); final OWLClassA resOne = findRequired(OWLClassA.class, entityA.getUri()); assertNull(resOne.getStringAttribute()); @@ -325,9 +328,9 @@ void retrieveLoadsStringLiteralWithCorrectLanguageTagWhenCachedValueHasDifferent persist(entityA); // persisted with @en final String value = "cestina"; persistTestData(Collections - .singleton( - new Quad(entityA.getUri(), URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), value, - "cs")), em); + .singleton( + new Quad(entityA.getUri(), URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), value, + "cs")), em); final OWLClassA resOne = findRequired(OWLClassA.class, entityA.getUri()); assertEquals(entityA.getStringAttribute(), resOne.getStringAttribute()); @@ -344,12 +347,13 @@ public void reloadAllowsToReloadFileStorageContent() throws Exception { final Map props = new HashMap<>(); final File storage = Files.createTempFile("reload-driver-test", ".owl").toFile(); storage.deleteOnExit(); - final String initialContent = "\n" + - "" + - "" + - ""; + final String initialContent = """ + + \ + \ + """; Files.write(storage.toPath(), initialContent.getBytes()); props.put(JOPAPersistenceProperties.ONTOLOGY_PHYSICAL_URI_KEY, storage.toURI().toString()); props.put(JOPAPersistenceProperties.ONTOLOGY_URI_KEY, "http://www.w3.org/TR/2003/PR-owl-guide-20031209/wine"); @@ -381,6 +385,7 @@ private static void replaceFileContents(File target) throws IOException { protected abstract void addFileStorageProperties(Map properties); @Test + @Disabled void getReferenceRetrievesReferenceToInstanceWithDataPropertiesWhoseAttributesAreLoadedLazily() throws Exception { this.em = getEntityManager( @@ -431,10 +436,10 @@ void loadingEntityWithLexicalFormAttributeLoadsLexicalFormOfLiteral() throws Exc persist(entityM); final Integer value = 117; persistTestData(Collections - .singleton( - new Quad(URI.create(entityM.getKey()), URI.create(Vocabulary.p_m_lexicalForm), - value)), - em); + .singleton( + new Quad(URI.create(entityM.getKey()), URI.create(Vocabulary.p_m_lexicalForm), + value)), + em); final OWLClassM result = findRequired(OWLClassM.class, entityM.getKey()); assertEquals(value.toString(), result.getLexicalForm()); } @@ -444,9 +449,9 @@ void loadingEntityWithSimpleLiteralLoadsSimpleLiteralValue() throws Exception { this.em = getEntityManager("loadingEntityWithSimpleLiteralLoadsSimpleLiteralValue", false); final String value = "test"; persistTestData(Arrays.asList( - new Quad(URI.create(entityM.getKey()), URI.create(RDF.TYPE), URI.create(Vocabulary.C_OWL_CLASS_M)), - new Quad(URI.create(entityM.getKey()), URI.create(Vocabulary.p_m_simpleLiteral), value, (String) null)), - em); + new Quad(URI.create(entityM.getKey()), URI.create(RDF.TYPE), URI.create(Vocabulary.C_OWL_CLASS_M)), + new Quad(URI.create(entityM.getKey()), URI.create(Vocabulary.p_m_simpleLiteral), value, (String) null)), + em); final OWLClassM result = findRequired(OWLClassM.class, entityM.getKey()); assertEquals(value, result.getSimpleLiteral()); @@ -468,10 +473,10 @@ void loadEntityWithSimpleLiteralLoadsAlsoLanguageTaggedValue() throws Exception void loadEntitySupportsCollectionAttribute() throws Exception { this.em = getEntityManager("loadEntitySupportsCollectionAttribute", false); persistTestData(Arrays.asList( - new Quad(URI.create(entityM.getKey()), URI.create(RDF.TYPE), URI.create(Vocabulary.C_OWL_CLASS_M)), - new Quad(URI.create(entityM.getKey()), URI.create(Vocabulary.p_m_StringCollection), "value", "en"), - new Quad(URI.create(entityM.getKey()), URI.create(Vocabulary.p_m_StringCollection), "valueTwo", "en")), - em); + new Quad(URI.create(entityM.getKey()), URI.create(RDF.TYPE), URI.create(Vocabulary.C_OWL_CLASS_M)), + new Quad(URI.create(entityM.getKey()), URI.create(Vocabulary.p_m_StringCollection), "value", "en"), + new Quad(URI.create(entityM.getKey()), URI.create(Vocabulary.p_m_StringCollection), "valueTwo", "en")), + em); final OWLClassM result = findRequired(OWLClassM.class, entityM.getKey()); assertNotNull(result.getStringCollection()); @@ -507,6 +512,7 @@ void testRetrieveEntityWithManagedTypeQueryAttr() { } @Test + @Disabled void testRetrieveWithLazyQueryAttribute() throws Exception { this.em = getEntityManager("RetrieveLazyQueryAttr", false); @@ -519,7 +525,7 @@ void testRetrieveWithLazyQueryAttribute() throws Exception { final Field f = OWLClassWithQueryAttr6.class.getDeclaredField("pluralQueryAttribute"); f.setAccessible(true); Object value = f.get(res); - assertNull(value); + assertInstanceOf(LazyLoadingProxy.class, value); assertNotNull(res.getPluralQueryAttribute()); value = f.get(res); assertNotNull(value); @@ -560,10 +566,41 @@ void retrieveSupportsMappingEnumsToSimpleLiterals() throws Exception { persistTestData(List.of( new Quad(URI.create(entityM.getKey()), URI.create(RDF.TYPE), URI.create(Vocabulary.C_OWL_CLASS_M)), new Quad(URI.create(entityM.getKey()), URI.create(Vocabulary.p_m_enumSimpleLiteralAttribute), - entityM.getEnumSimpleLiteral().name(), (String) null) + entityM.getEnumSimpleLiteral().name(), (String) null) ), em); final OWLClassM result = findRequired(OWLClassM.class, entityM.getKey()); assertEquals(entityM.getEnumSimpleLiteral(), result.getEnumSimpleLiteral()); } + + @Test + void retrieveViaReflectiveGetter() throws Exception { + this.em = getEntityManager("retrieveViaReflectiveGetter", false); + transactional(() -> { + em.persist(entityC); + entityC.setReferencedList(List.of(entityA)); + entityC.getReferencedList().forEach(em::persist); + }); + em.clear(); + + final OWLClassC result = em.find(OWLClassC.class, entityC.getUri()); + final Method getter = OWLClassC.class.getDeclaredMethod("getReferencedList"); + final Object value = getter.invoke(result); + assertNotNull(value); + assertFalse(((List) value).isEmpty()); + + assertEquals(1, ((List) value).size()); + assertThat(result.getReferencedList(), not(instanceOf(LazyLoadingProxy.class))); + } + + @Test + void lazilyLoadedAttributeIsNullWhenThereIsNoReferenceToLoad() { + this.em = getEntityManager("lazilyLoadedAttributeIsNullWhenThereIsNoReferenceToLoad", false); + final EntityDescriptor descriptor = new EntityDescriptor(Generators.generateUri()); + entityI.setOwlClassA(null); + transactional(() -> em.persist(entityI, descriptor)); + + final OWLClassI result = findRequired(OWLClassI.class, entityI.getUri()); + assertNull(result.getOwlClassA()); + } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsWithInheritanceRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsWithInheritanceRunner.java index 2e874e519..c0f11e45f 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsWithInheritanceRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsWithInheritanceRunner.java @@ -19,12 +19,13 @@ import cz.cvut.kbss.jopa.model.EntityManager; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.oom.exceptions.AmbiguousEntityTypeException; +import cz.cvut.kbss.jopa.oom.exception.AmbiguousEntityTypeException; import cz.cvut.kbss.jopa.test.*; import cz.cvut.kbss.jopa.test.environment.DataAccessor; import cz.cvut.kbss.jopa.test.environment.Generators; import cz.cvut.kbss.jopa.test.environment.PersistenceFactory; import cz.cvut.kbss.jopa.test.environment.Quad; +import cz.cvut.kbss.jopa.vocabulary.DC; import cz.cvut.kbss.jopa.vocabulary.RDF; import cz.cvut.kbss.jopa.vocabulary.RDFS; import org.junit.jupiter.api.Test; @@ -99,7 +100,7 @@ private Collection triplesForEntityT() { entityT.setUri(Generators.generateUri()); data.add(new Quad(entityT.getUri(), URI.create(RDF.TYPE), URI.create(Vocabulary.C_OWL_CLASS_T))); data.add(new Quad(entityT.getUri(), URI.create(RDFS.LABEL), entityT.getName())); - data.add(new Quad(entityT.getUri(), URI.create(Vocabulary.DC_DESCRIPTION), entityT.getDescription())); + data.add(new Quad(entityT.getUri(), URI.create(DC.Terms.DESCRIPTION), entityT.getDescription())); data.add(new Quad(entityT.getUri(), URI.create(Vocabulary.P_T_INTEGER_ATTRIBUTE), entityT.getIntAttribute())); data.addAll(triplesForA()); data.add(new Quad(entityT.getUri(), URI.create(Vocabulary.P_HAS_OWL_CLASS_A), entityA.getUri())); @@ -132,7 +133,7 @@ void findLoadsSubclassWhenSuperclassIsPassedInAndTypeCorrespondsToSubclass() thr final OWLClassS result = em.find(OWLClassS.class, entityT.getUri()); assertNotNull(result); - assertTrue(result instanceof OWLClassT); + assertInstanceOf(OWLClassT.class, result); verifyEntityTAttributes((OWLClassT) result); } @@ -149,14 +150,14 @@ void findLoadsSubclassOfAbstractParent() throws Exception { entityT.setUri(Generators.generateUri()); data.add(new Quad(entityT.getUri(), URI.create(RDF.TYPE), URI.create(Vocabulary.C_OWL_CLASS_S))); data.add(new Quad(entityT.getUri(), URI.create(RDFS.LABEL), entityT.getName())); - data.add(new Quad(entityT.getUri(), URI.create(Vocabulary.DC_DESCRIPTION), entityT.getDescription())); + data.add(new Quad(entityT.getUri(), URI.create(DC.Terms.DESCRIPTION), entityT.getDescription())); final EntityManager em = getEntityManager("findLoadsSubclassOfAbstractParent", false); persistTestData(data, em); final OWLClassSParent result = em.find(OWLClassSParent.class, entityT.getUri()); assertNotNull(result); - assertTrue(result instanceof OWLClassS); + assertInstanceOf(OWLClassS.class, result); final OWLClassS sResult = (OWLClassS) result; assertEquals(entityT.getName(), sResult.getName()); assertEquals(entityT.getDescription(), sResult.getDescription()); @@ -172,7 +173,7 @@ void findLoadsMostConcreteSubclassOfAbstractAncestor() throws Exception { final OWLClassSParent result = em.find(OWLClassSParent.class, entityT.getUri()); assertNotNull(result); - assertTrue(result instanceof OWLClassT); + assertInstanceOf(OWLClassT.class, result); verifyEntityTAttributes((OWLClassT) result); } @@ -200,7 +201,7 @@ void findReturnsMostSpecificSubtypeWhenReturnTypeIsAbstractAncestor() throws Exc final OWLClassSParent result = em.find(OWLClassSParent.class, entityT.getUri()); assertNotNull(result); - assertTrue(result instanceof OWLClassT); + assertInstanceOf(OWLClassT.class, result); verifyEntityTAttributes((OWLClassT) result); } @@ -211,7 +212,7 @@ void findLoadsMostSpecificSubclassFromCache() { final OWLClassSParent result = em.find(OWLClassSParent.class, entityT.getUri()); assertNotNull(result); - assertTrue(result instanceof OWLClassT); + assertInstanceOf(OWLClassT.class, result); verifyEntityTAttributes((OWLClassT) result); } @@ -277,11 +278,10 @@ void loadingEntityLoadsCorrectSubtypeInPolymorphicAttribute() { persist(entityU, entityT, entityA); final OWLClassU result = em.find(OWLClassU.class, entityU.getUri()); - assertNotNull(result.getOwlClassS()); - assertTrue(result.getOwlClassS() instanceof OWLClassT); + assertEquals(entityT.getUri(), result.getOwlClassS().getUri()); + assertInstanceOf(OWLClassT.class, result.getOwlClassS()); final OWLClassT tResult = (OWLClassT) result.getOwlClassS(); verifyEntityTAttributes(tResult); - assertNotNull(tResult.getOwlClassA()); assertEquals(entityA.getUri(), tResult.getOwlClassA().getUri()); } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsRunner.java index 8a2e64958..028f78f91 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsRunner.java @@ -22,7 +22,8 @@ import cz.cvut.kbss.jopa.model.EntityManager; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.oom.exceptions.UnpersistedChangeException; +import cz.cvut.kbss.jopa.oom.exception.UnpersistedChangeException; +import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy; import cz.cvut.kbss.jopa.test.OWLClassA; import cz.cvut.kbss.jopa.test.OWLClassB; import cz.cvut.kbss.jopa.test.OWLClassD; @@ -50,7 +51,6 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; -import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; @@ -74,6 +74,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -112,16 +113,14 @@ private void initialize() { } @Test - void testUpdateDataLeaveLazy() throws Exception { + void testUpdateDataLeaveLazy() { this.em = getEntityManager("UpdateDataProperty", false); entityB.setProperties(Generators.createProperties()); persist(entityB); em.getTransaction().begin(); final OWLClassB b = findRequired(OWLClassB.class, entityB.getUri()); - final Field propsField = OWLClassB.class.getDeclaredField("properties"); - propsField.setAccessible(true); - assertNull(propsField.get(b)); + assertInstanceOf(LazyLoadingProxy.class, b.getProperties()); final String newString = "NewString"; b.setStringAttribute(newString); em.getTransaction().commit(); @@ -129,6 +128,8 @@ void testUpdateDataLeaveLazy() throws Exception { final OWLClassB res = findRequired(OWLClassB.class, entityB.getUri()); assertEquals(newString, res.getStringAttribute()); assertNotNull(res.getProperties()); + // Trigger lazy loading + assertFalse(res.getProperties().isEmpty()); assertEquals(entityB.getProperties(), res.getProperties()); } @@ -1150,7 +1151,7 @@ void mergeHandlesCascadingAndReferencedObjects() { * Bug #202 */ @Test - void concurrentTransactionsLeaveDataInConsistentState() { + public void concurrentTransactionsLeaveDataInConsistentState() { final String a1String = "a1String"; final String a2String = "a2String"; this.em= getEntityManager("concurrentTransactionsLeaveDataInConsistentState", false); diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateWithInferenceRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateWithInferenceRunner.java index 1c403e672..5bb8808b3 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateWithInferenceRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateWithInferenceRunner.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.test.runner; import cz.cvut.kbss.jopa.exceptions.InferredAttributeModifiedException; +import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; import cz.cvut.kbss.jopa.test.OWLClassF; import cz.cvut.kbss.jopa.test.OWLClassW; import cz.cvut.kbss.jopa.test.Vocabulary; @@ -33,8 +34,15 @@ import java.util.Collections; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.*; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Testing implementation of Feature #121 - editable inferred attributes. @@ -84,7 +92,7 @@ public void additiveChangeToAttributeWithInferredValuesWorks() throws Exception final OWLClassW entityW = new OWLClassW(); persistTestData(Collections.singleton( new Quad(URI.create(Vocabulary.C_OWL_CLASS_W), URI.create(RDFS.SUB_CLASS_OF), - URI.create(Vocabulary.C_OWL_CLASS_A))), em); + URI.create(Vocabulary.C_OWL_CLASS_A))), em); persist(entityW); final URI newType = Generators.generateUri(); @@ -107,7 +115,7 @@ public void removalOfAssertedValueOfInferredAttributeWorks() throws Exception { final URI typeToAdd = Generators.generateUri(); persistTestData(Collections.singleton( new Quad(URI.create(Vocabulary.C_OWL_CLASS_W), URI.create(RDFS.SUB_CLASS_OF), - URI.create(Vocabulary.C_OWL_CLASS_A))), em); + URI.create(Vocabulary.C_OWL_CLASS_A))), em); persist(entityW); transactional(() -> { @@ -129,16 +137,21 @@ public void removalOfInferredValueOfInferredAttributeThrowsInferredAttributeModi final OWLClassW entityW = new OWLClassW(); persistTestData(Collections.singleton( new Quad(URI.create(Vocabulary.C_OWL_CLASS_W), URI.create(RDFS.SUB_CLASS_OF), - typeToRemove)), em); + typeToRemove)), em); persist(entityW); - assertThrows(InferredAttributeModifiedException.class, () -> { + final OWLPersistenceException ex = assertThrows(OWLPersistenceException.class, () -> { em.getTransaction().begin(); final OWLClassW toUpdate = findRequired(OWLClassW.class, entityW.getUri()); assertThat(toUpdate.getTypes(), hasItem(typeToRemove)); toUpdate.getTypes().remove(typeToRemove); em.getTransaction().commit(); }); + // Depending on the change tracking strategy, the exception may be thrown immediately on change and being InferredAttributeException + // Or on commit, in which case it will be a RollbackException with InferredAttributeModifiedException as cause + if (!(ex instanceof InferredAttributeModifiedException)) { + assertInstanceOf(InferredAttributeModifiedException.class, ex.getCause()); + } } @Test @@ -149,7 +162,7 @@ public void removalOfAssertedValueDoesNotAssertInferredValues() throws Exception final OWLClassW entityW = new OWLClassW(); entityW.setTypes(Collections.singleton(typeToRemove)); persistTestData(Collections.singleton(new Quad(typeToRemove, URI.create(RDFS.SUB_CLASS_OF), inferredSupertype)), - em); + em); persist(entityW); final OWLClassW toUpdate = findRequired(OWLClassW.class, entityW.getUri()); 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 cf374609e..89c526350 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 @@ -18,27 +18,48 @@ package cz.cvut.kbss.jopa.test.integration; import cz.cvut.kbss.jopa.exceptions.RollbackException; -import cz.cvut.kbss.jopa.model.annotations.*; -import cz.cvut.kbss.jopa.oom.exceptions.UnpersistedChangeException; -import cz.cvut.kbss.jopa.test.*; +import cz.cvut.kbss.jopa.model.annotations.Id; +import cz.cvut.kbss.jopa.model.annotations.MappedSuperclass; +import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty; +import cz.cvut.kbss.jopa.model.annotations.OWLClass; +import cz.cvut.kbss.jopa.model.annotations.PrePersist; +import cz.cvut.kbss.jopa.oom.exception.UnpersistedChangeException; +import cz.cvut.kbss.jopa.test.OWLClassA; +import cz.cvut.kbss.jopa.test.OWLClassD; +import cz.cvut.kbss.jopa.test.OWLClassF; +import cz.cvut.kbss.jopa.test.OWLClassJ; +import cz.cvut.kbss.jopa.test.OWLClassR; +import cz.cvut.kbss.jopa.test.Vocabulary; import cz.cvut.kbss.jopa.test.environment.Generators; +import cz.cvut.kbss.jopa.utils.JOPALazyUtils; import cz.cvut.kbss.jopa.vocabulary.RDFS; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; -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.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import java.net.URI; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @@ -109,33 +130,25 @@ private void initAxiomsForOwlClassD(NamedResource subject) throws OntoDriverExce * entity, nothing should be added into a plural attribute collection. The bug caused {@code null} to be added. */ @Test - void readingInstanceReferenceWithoutCorrectTypeResultsInNullAddedToPluralAttribute() throws OntoDriverException { + void readingInstanceReferenceWithoutCorrectTypeDoesNotAddAnythingToPluralAttribute() throws OntoDriverException { final URI owner = Generators.generateUri(); initAxiomsForNullReferenceLoad(owner); final OWLClassJ result = em.find(OWLClassJ.class, owner); assertNotNull(result); - assertThat(result.getOwlClassA(), anyOf(nullValue(), empty())); + assertTrue(result.getOwlClassA().isEmpty()); } private void initAxiomsForNullReferenceLoad(URI owner) throws OntoDriverException { final NamedResource ownerResource = NamedResource.create(owner); final Assertion classAssertion = Assertion.createClassAssertion(false); - final NamedResource reference = NamedResource.create(Generators.generateUri()); final Assertion opAssertion = Assertion .createObjectPropertyAssertion(URI.create(Vocabulary.P_HAS_OWL_CLASS_A), false); final AxiomDescriptor fDesc = new AxiomDescriptor(ownerResource); fDesc.addAssertion(classAssertion); + fDesc.addAssertion(opAssertion); when(connectionMock.find(fDesc)) .thenReturn(Collections.singletonList(new AxiomImpl<>(ownerResource, classAssertion, new Value<>(NamedResource.create(Vocabulary.C_OWL_CLASS_J))))); - final AxiomDescriptor refDescriptor = new AxiomDescriptor(ownerResource); - refDescriptor.addAssertion(opAssertion); - when(connectionMock.find(refDescriptor)).thenReturn( - Collections.singletonList(new AxiomImpl<>(ownerResource, opAssertion, new Value<>(reference)))); - final AxiomDescriptor aDesc = new AxiomDescriptor(reference); - aDesc.addAssertion(classAssertion); - aDesc.addAssertion(Assertion.createDataPropertyAssertion(URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), false)); - when(connectionMock.find(aDesc)).thenReturn(Collections.emptyList()); } /** @@ -180,7 +193,7 @@ void prePersistChild() { } @Test - void getterOnLazyAttributeWithNullValueAfterPersistDoesNotTriggerLazyFetch() { + void exceptionInCommitIsWrappedInRollbackException() { final OWLClassF owner = new OWLClassF(Generators.generateUri()); final OWLClassA a = new OWLClassA(); owner.setSimpleSet(new HashSet<>(Collections.singletonList(a))); @@ -189,4 +202,44 @@ void getterOnLazyAttributeWithNullValueAfterPersistDoesNotTriggerLazyFetch() { final RollbackException ex = assertThrows(RollbackException.class, () -> em.getTransaction().commit()); assertInstanceOf(UnpersistedChangeException.class, ex.getCause()); } + + @Test + void triggeringLazyLoadingMultipleTimesDoesNotLeadToStackOverflow() throws Exception { + final OWLClassF owner = new OWLClassF(Generators.generateUri()); + final OWLClassA a = new OWLClassA(Generators.generateUri()); + a.setStringAttribute("Does not matter"); + owner.setSimpleSet(Set.of(a)); + initOWLClassFAxioms(owner); + initAxiomsForOWLClassA(NamedResource.create(a.getUri()), a.getStringAttribute(), false); + + em.getTransaction().begin(); + final OWLClassF instance = em.find(OWLClassF.class, owner.getUri()); + assertNotNull(instance); + final Set proxy = instance.getSimpleSet(); + assertFalse(JOPALazyUtils.isLoaded(proxy)); + // Triggers lazy loading + assertFalse(proxy.isEmpty()); + instance.setSimpleSet(proxy); + assertFalse(proxy.isEmpty()); + } + + private void initOWLClassFAxioms(OWLClassF instance) throws Exception { + final NamedResource subject = NamedResource.create(instance.getUri()); + final List> axioms = new ArrayList<>(); + final Axiom classAssertion = new AxiomImpl<>(subject, Assertion.createClassAssertion(false), + new Value<>(NamedResource.create(Vocabulary.C_OWL_CLASS_F))); + final Assertion aSetAssertion = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_F_HAS_SIMPLE_SET), false); + axioms.add(classAssertion); + final List> aSetAxioms = instance.getSimpleSet().stream().map(a -> new AxiomImpl<>(subject, aSetAssertion, new Value<>(NamedResource.create(a.getUri())))).toList(); + axioms.addAll(aSetAxioms); + final AxiomDescriptor entityDesc = new AxiomDescriptor(subject); + entityDesc.addAssertion(Assertion.createClassAssertion(false)); + entityDesc.addAssertion(aSetAssertion); + entityDesc.addAssertion(Assertion.createDataPropertyAssertion(URI.create(Vocabulary.P_F_STRING_ATTRIBUTE), true)); + doReturn(axioms).when(connectionMock).find(entityDesc); + + final AxiomDescriptor setDesc = new AxiomDescriptor(subject); + setDesc.addAssertion(aSetAssertion); + doReturn(aSetAxioms).when(connectionMock).find(setDesc); + } } diff --git a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/CacheTest.java b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/CacheTest.java index 9d3af081e..be5496e64 100644 --- a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/CacheTest.java +++ b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/CacheTest.java @@ -17,10 +17,10 @@ */ package cz.cvut.kbss.jopa.test.integration; -import cz.cvut.kbss.jopa.adapters.IndirectSet; +import cz.cvut.kbss.jopa.proxy.change.ChangeTrackingIndirectSet; import cz.cvut.kbss.jopa.model.EntityManager; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; -import cz.cvut.kbss.jopa.sessions.CacheManager; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; import cz.cvut.kbss.jopa.test.OWLClassA; import cz.cvut.kbss.jopa.test.OWLClassB; import cz.cvut.kbss.jopa.test.OWLClassF; @@ -49,6 +49,9 @@ import java.util.Collection; import java.util.Collections; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -90,12 +93,9 @@ void queryResultIsLoadedFromCacheWhenItIsAlreadyCached() throws Exception { when(connectionMock.find(any())).thenReturn(axiomsForA(instanceUri)); final OWLClassA firstA = em.find(OWLClassA.class, instanceUri); assertNotNull(firstA); - final EntityManager emTwo = emf.createEntityManager(); - try { + try (EntityManager emTwo = emf.createEntityManager()) { final OWLClassA secondA = emTwo.createNativeQuery(query, OWLClassA.class).getSingleResult(); assertNotNull(secondA); - } finally { - emTwo.close(); } verify(connectionMock).find(any(AxiomDescriptor.class)); } @@ -124,7 +124,7 @@ void loadedInstanceAddedToCacheDoesNotContainIndirectCollection() throws Excepti final OWLClassA result = cacheManager.get(OWLClassA.class, id, new EntityDescriptor()); assertNotNull(result); assertNotNull(result.getTypes()); - assertFalse(result.getTypes() instanceof IndirectSet); + assertThat(result.getTypes(), not(instanceOf(ChangeTrackingIndirectSet.class))); } @Test diff --git a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/DuplicateIdentifiersTest.java b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/DuplicateIdentifiersTest.java index 33ec3bd79..d0d23b309 100644 --- a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/DuplicateIdentifiersTest.java +++ b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/DuplicateIdentifiersTest.java @@ -63,21 +63,13 @@ private void initInstances() { @Test void persistObjectTwiceInPersistenceContextIsLegal() throws Exception { + when(connectionMock.types()).thenReturn(typesMock); em.getTransaction().begin(); em.persist(entityA); - entityA.setStringAttribute("UpdatedString"); em.persist(entityA); em.getTransaction().commit(); verify(connectionMock).persist(any(AxiomValueDescriptor.class)); - final ArgumentCaptor captor = ArgumentCaptor.forClass(AxiomValueDescriptor.class); - verify(connectionMock).update(captor.capture()); - final AxiomValueDescriptor descriptor = captor.getValue(); - assertEquals(1, descriptor.getAssertions().size()); - final List> values = descriptor.getAssertionValues(Assertion.createDataPropertyAssertion(URI.create( - Vocabulary.P_A_STRING_ATTRIBUTE), false)); - assertEquals(1, values.size()); - assertEquals(entityA.getStringAttribute(), values.get(0).getValue()); } @Test @@ -98,6 +90,7 @@ void persistTwoInstancesOfDifferentClassesWithSameIdentifierInOnePersistenceCont @Test void persistTwoInstancesOfDifferentClassesWithSameIdentifierInDifferentPersistenceContextsIsLegal() throws Exception { + when(connectionMock.types()).thenReturn(typesMock); final OWLClassB entityB = new OWLClassB(); entityB.setUri(entityA.getUri()); entityB.setStringAttribute("bStringAttribute"); @@ -117,6 +110,7 @@ void persistTwoInstancesOfDifferentClassesWithSameIdentifierInDifferentPersisten @Test void mergeInstanceTwiceInTwoPersistenceContextsIsLegal() throws Exception { + when(connectionMock.types()).thenReturn(typesMock); final NamedResource subject = NamedResource.create(entityA.getUri()); final Assertion stringAss = Assertion .createDataPropertyAssertion(URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), false); @@ -124,7 +118,7 @@ void mergeInstanceTwiceInTwoPersistenceContextsIsLegal() throws Exception { em.getTransaction().begin(); em.persist(entityA); em.getTransaction().commit(); - initAxiomsForOWLClassA(subject, stringAss, entityA.getStringAttribute()); + initAxiomsForOWLClassA(subject, entityA.getStringAttribute(), false); when(connectionMock.contains( new AxiomImpl<>(subject, Assertion.createClassAssertion(false), new Value<>(NamedResource.create(OWLClassA.getClassIri()))), Collections.emptySet())).thenReturn(true); @@ -153,6 +147,7 @@ void mergeInstanceTwiceInTwoPersistenceContextsIsLegal() throws Exception { @Test void mergeTwoInstancesWithTheSameIdentifierInTwoPersistenceContextsIsLegal() throws Exception { + when(connectionMock.types()).thenReturn(typesMock); final NamedResource subject = NamedResource.create(entityA.getUri()); final Assertion stringAssA = Assertion .createDataPropertyAssertion(URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), false); @@ -162,7 +157,7 @@ void mergeTwoInstancesWithTheSameIdentifierInTwoPersistenceContextsIsLegal() thr em.getTransaction().begin(); em.persist(entityA); em.getTransaction().commit(); - initAxiomsForOWLClassA(subject, stringAssA, entityA.getStringAttribute()); + initAxiomsForOWLClassA(subject, entityA.getStringAttribute(), true); final OWLClassB entityB = new OWLClassB(); entityB.setUri(entityA.getUri()); @@ -214,7 +209,7 @@ void mergeSameInstanceMultipleTimesInOnePersistenceContextIsLegal() throws Excep final NamedResource subject = NamedResource.create(entityA.getUri()); final Assertion stringAssA = Assertion .createDataPropertyAssertion(URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), false); - initAxiomsForOWLClassA(subject, stringAssA, entityA.getStringAttribute()); + initAxiomsForOWLClassA(subject, entityA.getStringAttribute(), true); final String updateOne = "updatedString"; entityA.setStringAttribute(updateOne); @@ -240,11 +235,9 @@ void mergeSameInstanceMultipleTimesInOnePersistenceContextIsLegal() throws Excep void mergeTwoInstancesOfDifferentClassesWithTheSameIdentifierIntoOnePersistenceContextIsIllegal() throws Exception { final NamedResource subject = NamedResource.create(entityA.getUri()); - final Assertion stringAssA = Assertion - .createDataPropertyAssertion(URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), false); final Assertion stringAssB = Assertion .createDataPropertyAssertion(URI.create(Vocabulary.P_B_STRING_ATTRIBUTE), false); - initAxiomsForOWLClassA(subject, stringAssA, entityA.getStringAttribute()); + initAxiomsForOWLClassA(subject, entityA.getStringAttribute(), false); final OWLClassB entityB = new OWLClassB(); entityB.setUri(entityA.getUri()); entityB.setStringAttribute("bStringAttribute"); @@ -266,7 +259,7 @@ void mergeTwoInstancesOfTheSameClassWithTheSameIdentifierIntoTheSamePersistenceC final NamedResource subject = NamedResource.create(entityA.getUri()); final Assertion stringAssA = Assertion .createDataPropertyAssertion(URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), false); - initAxiomsForOWLClassA(subject, stringAssA, entityA.getStringAttribute()); + initAxiomsForOWLClassA(subject, entityA.getStringAttribute(), true); final String updateOne = "update"; entityA.setStringAttribute(updateOne); @@ -292,9 +285,7 @@ void mergeTwoInstancesOfTheSameClassWithTheSameIdentifierIntoTheSamePersistenceC @Test void findTwiceInOnePersistenceContextWithTheSameIdentifierAndTypeReturnsTheSameInstance() throws Exception { final NamedResource subject = NamedResource.create(entityA.getUri()); - final Assertion stringAssA = Assertion - .createDataPropertyAssertion(URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), false); - initAxiomsForOWLClassA(subject, stringAssA, entityA.getStringAttribute()); + initAxiomsForOWLClassA(subject, entityA.getStringAttribute(), false); final OWLClassA aOne = em.find(OWLClassA.class, entityA.getUri()); assertNotNull(aOne); @@ -306,9 +297,7 @@ void findTwiceInOnePersistenceContextWithTheSameIdentifierAndTypeReturnsTheSameI @Test void findIndividualAsDifferentTypeThanIsAlreadyLoadedInPersistenceContextIsIllegal() throws Exception { final NamedResource subject = NamedResource.create(entityA.getUri()); - final Assertion stringAssA = Assertion - .createDataPropertyAssertion(URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), false); - initAxiomsForOWLClassA(subject, stringAssA, entityA.getStringAttribute()); + initAxiomsForOWLClassA(subject, entityA.getStringAttribute(), false); final OWLClassB entityB = new OWLClassB(); entityB.setUri(entityA.getUri()); entityB.setStringAttribute("bStringAttribute"); diff --git a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/IntegrationTestBase.java b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/IntegrationTestBase.java index fb48bb549..8e244bc4b 100644 --- a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/IntegrationTestBase.java +++ b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/IntegrationTestBase.java @@ -21,16 +21,23 @@ import cz.cvut.kbss.jopa.model.EntityManagerFactory; import cz.cvut.kbss.jopa.model.EntityManagerFactoryImpl; import cz.cvut.kbss.jopa.test.OWLClassA; +import cz.cvut.kbss.jopa.test.Vocabulary; import cz.cvut.kbss.jopa.test.integration.environment.PersistenceFactory; import cz.cvut.kbss.jopa.test.integration.environment.TestDataSource; import cz.cvut.kbss.ontodriver.Connection; +import cz.cvut.kbss.ontodriver.Types; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; -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.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.mockito.Mock; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -45,6 +52,9 @@ public class IntegrationTestBase { @Mock Connection connectionMock; + @Mock + Types typesMock; + @BeforeEach protected void setUp() throws Exception { this.emf = PersistenceFactory.initPersistence(Collections.emptyMap()); @@ -64,17 +74,20 @@ private TestDataSource getDataSource() { return ((EntityManagerFactoryImpl) emf).getServerSession().unwrap(TestDataSource.class); } - void initAxiomsForOWLClassA(NamedResource subject, Assertion stringAss, String stringAtt) + void initAxiomsForOWLClassA(NamedResource subject, String stringAtt, boolean withContains) throws OntoDriverException { final List> axioms = new ArrayList<>(); final Axiom classAssertion = new AxiomImpl<>(subject, Assertion.createClassAssertion(false), new Value<>(NamedResource.create(OWLClassA.getClassIri()))); + final Assertion stringAssertion = Assertion.createDataPropertyAssertion(URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), false); axioms.add(classAssertion); - axioms.add(new AxiomImpl<>(subject, stringAss, new Value<>(stringAtt))); + axioms.add(new AxiomImpl<>(subject, stringAssertion, new Value<>(stringAtt))); final AxiomDescriptor desc = new AxiomDescriptor(subject); desc.addAssertion(Assertion.createClassAssertion(false)); - desc.addAssertion(stringAss); + desc.addAssertion(stringAssertion); doReturn(axioms).when(connectionMock).find(desc); - doReturn(true).when(connectionMock).contains(classAssertion, Collections.emptySet()); + if (withContains) { + doReturn(true).when(connectionMock).contains(classAssertion, Collections.emptySet()); + } } } diff --git a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/PersistenceUnitUtilTest.java b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/PersistenceUnitUtilTest.java index e8a933edb..030d0182e 100644 --- a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/PersistenceUnitUtilTest.java +++ b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/PersistenceUnitUtilTest.java @@ -22,7 +22,11 @@ import cz.cvut.kbss.jopa.test.Vocabulary; import cz.cvut.kbss.jopa.test.environment.Generators; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; -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.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -30,10 +34,11 @@ import org.mockito.quality.Strictness; import java.net.URI; -import java.util.ArrayList; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -52,15 +57,14 @@ void isLoadedReturnsTrueForNewlyRegisteredEntity() { @Test void isLoadedReturnsTrueForLoadedExistingEntity() throws Exception { final URI uri = Generators.generateUri(); - initAxiomsForOWLClassA(NamedResource.create(uri), - Assertion.createDataPropertyAssertion(URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), false), "test"); + initAxiomsForOWLClassA(NamedResource.create(uri), "test", false); final OWLClassA entity = em.find(OWLClassA.class, uri); assertTrue(em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(entity)); assertTrue(em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(entity, "stringAttribute")); } @Test - void isLoadedReturnsFalseForUnloadedLazilyLoadedAttribute() throws Exception { + void isLoadedReturnsFalseForUnloadedLazilyLoadedNonEmptyAttribute() throws Exception { final URI uri = Generators.generateUri(); initOwlClassLAxioms(uri); final OWLClassL entity = em.find(OWLClassL.class, uri); @@ -73,12 +77,25 @@ void isLoadedReturnsFalseForUnloadedLazilyLoadedAttribute() throws Exception { private void initOwlClassLAxioms(URI uri) throws Exception { final NamedResource subject = NamedResource.create(uri); - final List> axioms = new ArrayList<>(); + final Assertion simpleList = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_l_simpleListAttribute), false); + final Assertion referencedList = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_l_referencedListAttribute), false); + final Assertion aSet = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_l_aSetAttribute), false); + final Assertion singleA = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.p_l_singleOwlClassAAttribute), false); final Axiom classAssertion = new AxiomImpl<>(subject, Assertion.createClassAssertion(false), new Value<>(NamedResource.create(Vocabulary.C_OWL_CLASS_L))); - axioms.add(classAssertion); + final List> axioms = List.of( + classAssertion, + new AxiomImpl<>(subject, simpleList, new Value<>(NamedResource.create(Generators.generateUri()))), + new AxiomImpl<>(subject, referencedList, new Value<>(NamedResource.create(Generators.generateUri()))), + new AxiomImpl<>(subject, aSet, new Value<>(NamedResource.create(Generators.generateUri()))), + new AxiomImpl<>(subject, singleA, new Value<>(NamedResource.create(Generators.generateUri()))) + ); final AxiomDescriptor desc = new AxiomDescriptor(subject); desc.addAssertion(Assertion.createClassAssertion(false)); + desc.addAssertion(simpleList); + desc.addAssertion(referencedList); + desc.addAssertion(aSet); + desc.addAssertion(singleA); when(connectionMock.find(desc)).thenReturn(axioms); when(connectionMock.contains(classAssertion, null)).thenReturn(true); } diff --git a/jopa-maven-plugin/pom.xml b/jopa-maven-plugin/pom.xml index 6e0e459be..2f2267465 100644 --- a/jopa-maven-plugin/pom.xml +++ b/jopa-maven-plugin/pom.xml @@ -5,7 +5,7 @@ jopa-all cz.cvut.kbss.jopa - 1.2.2 + 2.0.0 ../pom.xml 4.0.0 diff --git a/jopa-maven-plugin/src/main/java/cz/cvut/kbss/jopa/maven/OWL2JavaMojo.java b/jopa-maven-plugin/src/main/java/cz/cvut/kbss/jopa/maven/OWL2JavaMojo.java index e19a8f167..bfa9b8749 100644 --- a/jopa-maven-plugin/src/main/java/cz/cvut/kbss/jopa/maven/OWL2JavaMojo.java +++ b/jopa-maven-plugin/src/main/java/cz/cvut/kbss/jopa/maven/OWL2JavaMojo.java @@ -42,6 +42,9 @@ public class OWL2JavaMojo extends AbstractMojo { private static final String PREFER_MULTILINGUAL_STRINGS = "prefer-multilingual-strings"; private static final String GENERATE_ANNOTATION_FIELDS = "generate-annotation-fields"; private static final String GENERATE_THING = "generate-thing"; + private static final String ONTOLOGY_PREFIX_PROPERTY = "ontology-prefix-property"; + private static final String ALWAYS_USE_PREFIXES = "always-use-prefixes"; + private static final String PREFIX_MAPPING_FILE = "prefix-mapping-file"; @Parameter(name = MAPPING_FILE_PARAM) private String mappingFile; @@ -85,6 +88,15 @@ public class OWL2JavaMojo extends AbstractMojo { @Parameter(name = GENERATE_THING, defaultValue = "true") private boolean generateThing; + @Parameter(name = ONTOLOGY_PREFIX_PROPERTY) + private String ontologyPrefixProperty; + + @Parameter(name = ALWAYS_USE_PREFIXES, defaultValue = "true") + private boolean alwaysUsePrefixes; + + @Parameter(name = PREFIX_MAPPING_FILE) + private String prefixMappingFile; + @Override public void execute() { OWL2JavaTransformer owl2java = new OWL2JavaTransformer(); @@ -117,7 +129,9 @@ public void execute() { final TransformationConfiguration config = builder.packageName(pPackage).targetDir(outputDirectory).addOwlapiIris(withOwlapi) .generateJavadoc(javadocFromRdfsComment).preferMultilingualStrings(preferMultilingualStrings) - .generateAnnotationFields(generateAnnotationFields).generateThing(generateThing).build(); + .generateAnnotationFields(generateAnnotationFields).generateThing(generateThing) + .ontologyPrefixProperty(ontologyPrefixProperty).alwaysUseOntologyPrefix(alwaysUsePrefixes) + .prefixMappingFile(prefixMappingFile).build(); if (vocabularyOnly) { owl2java.generateVocabulary(config); @@ -143,6 +157,9 @@ private void printParameterValues() { Utils.logParameterValue(PREFER_MULTILINGUAL_STRINGS, preferMultilingualStrings, getLog()); Utils.logParameterValue(GENERATE_ANNOTATION_FIELDS, generateAnnotationFields, getLog()); Utils.logParameterValue(GENERATE_THING, generateThing, getLog()); + Utils.logParameterValue(ONTOLOGY_PREFIX_PROPERTY, ontologyPrefixProperty, getLog()); + Utils.logParameterValue(ALWAYS_USE_PREFIXES, alwaysUsePrefixes, getLog()); + Utils.logParameterValue(PREFIX_MAPPING_FILE, prefixMappingFile, getLog()); } public String getPackage() { diff --git a/jopa-owl2java/pom.xml b/jopa-owl2java/pom.xml index 06e7443fc..adcbf84e6 100644 --- a/jopa-owl2java/pom.xml +++ b/jopa-owl2java/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/Card.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/Card.java index 9f412db24..bd073457e 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/Card.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/Card.java @@ -19,4 +19,4 @@ public enum Card { NO, ONE, MULTIPLE, LIST, SIMPLELIST, REFERENCEDLIST -} \ No newline at end of file +} diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/ClassObjectPropertyComputer.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/ClassObjectPropertyComputer.java index 14f76b91f..9fb92ea3f 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/ClassObjectPropertyComputer.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/ClassObjectPropertyComputer.java @@ -78,4 +78,4 @@ public ClassObjectPropertyComputer(final OWLClass clazz, } } } -} \ No newline at end of file +} diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/Constants.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/Constants.java index 63a378a9c..66a13d2d8 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/Constants.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/Constants.java @@ -17,6 +17,9 @@ */ package cz.cvut.kbss.jopa.owl2java; +import java.time.Duration; +import java.time.temporal.ChronoUnit; + public class Constants { /** @@ -75,6 +78,11 @@ public class Constants { */ public static final String PROPERTIES_FIELD_NAME = "properties"; + /** + * Timeout for resolving ontology prefix via a remote service. + */ + public static final Duration PREFIX_RESOLVE_TIMEOUT = Duration.of(5, ChronoUnit.SECONDS); + /** * Tool version. */ diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/ContextDefinition.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/ContextDefinition.java index 5dc96e78f..d69e976a0 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/ContextDefinition.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/ContextDefinition.java @@ -70,4 +70,4 @@ void add(OWLEntity e) { individuals.add(e.asOWLNamedIndividual()); } } -} \ No newline at end of file +} diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/JavaNameGenerator.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/JavaNameGenerator.java new file mode 100644 index 000000000..7556b6a31 --- /dev/null +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/JavaNameGenerator.java @@ -0,0 +1,129 @@ +package cz.cvut.kbss.jopa.owl2java; + +import cz.cvut.kbss.jopa.owl2java.prefix.PrefixMap; +import org.semanticweb.owlapi.model.IRI; +import org.semanticweb.owlapi.model.OWLOntologyID; + +import java.text.Normalizer; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; + +/** + * Generates Java names based on IRI identifiers. + */ +public class JavaNameGenerator { + + private static final String[] JAVA_KEYWORDS = {"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while"}; + + private static final char SEPARATOR = '_'; + + private final PrefixMap prefixMap; + + public JavaNameGenerator(PrefixMap prefixMap) {this.prefixMap = prefixMap;} + + /** + * Returns a valid Java identifier extracted from the specified IRI. + *

    + * If the IRI contains a non-empty fragment, it is used. Otherwise, the part after the last slash is used as the + * name. + * + * @param iri IRI to extract name from + * @return Java name based on the specified IRI + */ + public String generateJavaNameForIri(IRI iri) { + if (iri.getFragment() != null && !iri.getFragment().isEmpty()) { + return makeNameValidJava(iri.getFragment()); + } else { + String strIri = iri.toString(); + if (strIri.charAt(strIri.length() - 1) == '/') { + strIri = strIri.substring(0, strIri.length() - 1); + } + int x = strIri.lastIndexOf("/"); + return makeNameValidJava(strIri.substring(x + 1)); + } + } + + /** + * Returns a valid Java identifier extracted from the specified IRI, prefixed with prefix registered for the + * specified ontology IRI (if available). + *

    + * If the IRI contains a non-empty fragment, it is used. Otherwise, the part after the last slash is used as the + * name. + *

    + * If the ontology is anonymous, no prefix is added to the extracted name. If no prefix is registered for the + * ontology, a prefix represented by extracting a java name from the ontology IRI is used. + * + * @param iri IRI to extract name from + * @param ontologyId Ontology identifier + * @return Java name based on the specified IRI + */ + public String generatePrefixedJavaNameForIri(IRI iri, OWLOntologyID ontologyId) { + if (ontologyId.isAnonymous()) { + return generateJavaNameForIri(iri); + } + assert ontologyId.getOntologyIRI().isPresent(); + final IRI ontologyIri = ontologyId.getOntologyIRI().get(); + return makeNameValidJava(prefixMap.getPrefix(ontologyIri) + .orElse(generateJavaNameForIri(ontologyIri))) + SEPARATOR + generateJavaNameForIri(iri); + } + + /** + * Gets the prefix registered for an ontology with the specified identifier. + * + * @param ontologyIri Ontology IRI + * @return Optional ontology prefix + */ + public Optional getOntologyPrefix(IRI ontologyIri) { + Objects.requireNonNull(ontologyIri); + return prefixMap.getPrefix(ontologyIri); + } + + /** + * Checks whether a prefix exists for the specified ontology identifier. + * + * @param ontologyIri Ontology IRI + * @return {@code true} if a prefix has been resolved for ontology IRI, {@code false} otherwise + */ + public boolean hasPrefix(IRI ontologyIri) { + return prefixMap.hasPrefix(ontologyIri); + } + + /** + * Returns the specified name sanitized for Java. + *

    + * This means the result of this function can be used as/in a Java variable/field/class name. + * + * @param name The name to sanitize + * @return Valid Java identifier + */ + public static String makeNameValidJava(String name) { + String res = name.trim().replace('-', SEPARATOR).replace("'", "_quote_") + .replace(".", "_dot_").replace(',', '_') + .replace("#", ""); + // Replace non-ASCII characters with ASCII ones + res = Normalizer.normalize(res, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""); + if (Arrays.binarySearch(JAVA_KEYWORDS, res) >= 0) { + res = SEPARATOR + res; + } + return res; + } + + /** + * Converts the specified name to the Java camel case notation. + *

    + * This process removes underscores used to generate the name. + * + * @param name Generated name + * @return Converted camel case name + */ + public static String toCamelCaseNotation(String name) { + StringBuilder result = new StringBuilder(); + for (String w : name.split(Character.toString(SEPARATOR))) { + if (!w.isEmpty()) { + result.append(w.substring(0, 1).toUpperCase()).append(w.substring(1)); + } + } + return result.toString(); + } +} diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/JavaTransformer.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/JavaTransformer.java index e1c8be31e..41f78ddb9 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/JavaTransformer.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/JavaTransformer.java @@ -51,6 +51,7 @@ import cz.cvut.kbss.jopa.owl2java.cli.PropertiesType; import cz.cvut.kbss.jopa.owl2java.config.TransformationConfiguration; import cz.cvut.kbss.jopa.owl2java.exception.OWL2JavaException; +import cz.cvut.kbss.jopa.owl2java.prefix.PrefixMap; import cz.cvut.kbss.jopa.owlapi.DatatypeTransformer; import cz.cvut.kbss.jopa.vocabulary.DC; import cz.cvut.kbss.jopa.vocabulary.RDFS; @@ -60,14 +61,13 @@ import org.semanticweb.owlapi.model.OWLDatatype; import org.semanticweb.owlapi.model.OWLEntity; import org.semanticweb.owlapi.model.OWLOntology; +import org.semanticweb.owlapi.model.OWLOntologyID; import org.semanticweb.owlapi.model.OWLOntologyManager; import org.semanticweb.owlapi.search.EntitySearcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; -import java.text.Normalizer; -import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; @@ -95,57 +95,6 @@ public class JavaTransformer { private static final Logger LOG = LoggerFactory.getLogger(OWL2JavaTransformer.class); - private static final String[] KEYWORDS = {"abstract", - "assert", - "boolean", - "break", - "byte", - "case", - "catch", - "char", - "class", - "const", - "continue", - "default", - "do", - "double", - "else", - "enum", - "extends", - "final", - "finally", - "float", - "for", - "goto", - "if", - "implements", - "import", - "instanceof", - "int", - "interface", - "long", - "native", - "new", - "package", - "private", - "protected", - "public", - "return", - "short", - "static", - "strictfp", - "super", - "switch", - "synchronized", - "this", - "throw", - "throws", - "transient", - "try", - "void", - "volatile", - "while"}; - private static final String PREFIX_STRING = "s_"; private static final String PREFIX_CLASS = "c_"; private static final String PREFIX_PROPERTY = "p_"; @@ -158,50 +107,14 @@ public class JavaTransformer { private final Map classes = new HashMap<>(); + private JavaNameGenerator nameGenerator; + private final TransformationConfiguration configuration; JavaTransformer(TransformationConfiguration configuration) { this.configuration = configuration; } - private static String validJavaIDForIRI(final IRI iri) { - if (iri.getFragment() != null) { - return validJavaID(iri.getFragment()); - } else { - int x = iri.toString().lastIndexOf("/"); - return validJavaID(iri.toString().substring(x + 1)); - } - } - - private static String validJavaID(final String s) { - String res = s.trim().replace("-", "_").replace("'", "_quote_").replace(".", "_dot_").replace(',', '_'); - // Replace non-ASCII characters with ASCII ones - res = Normalizer.normalize(res, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""); - if (Arrays.binarySearch(KEYWORDS, res) >= 0) { - res = "_" + res; - } - return res; - } - - private static JFieldVar addField(final String name, final JDefinedClass cls, - final JType fieldType) { - String newName = name; - - int i = 0; - while (cls.fields().containsKey(newName)) { - newName = name + "" + (++i); - } - - final JFieldVar fvId = cls.field(JMod.PROTECTED, fieldType, newName); - final String fieldName = fvId.name().substring(0, 1).toUpperCase() + fvId.name().substring(1); - final JMethod mSetId = cls.method(JMod.PUBLIC, void.class, "set" + fieldName); - final JVar v = mSetId.param(fieldType, fvId.name()); - mSetId.body().assign(JExpr._this().ref(fvId), v); - final JMethod mGetId = cls.method(JMod.PUBLIC, fieldType, "get" + fieldName); - mGetId.body()._return(fvId); - return fvId; - } - /** * Generates an object model consisting of JOPA entity classes and a vocabulary file from the specified ontology and * context definition. @@ -212,6 +125,7 @@ private static JFieldVar addField(final String name, final JDefinedClass cls, */ public ObjectModel generateModel(final OWLOntology ontology, final ContextDefinition context) { try { + this.nameGenerator = new JavaNameGenerator(new PrefixMap(ontology.getOWLOntologyManager(), configuration)); final JCodeModel cm = new JCodeModel(); voc = createVocabularyClass(cm); generateVocabulary(ontology, cm, context); @@ -256,6 +170,7 @@ private static void generateAuthorshipDoc(JDocCommentable javaElem) { */ public ObjectModel generateVocabulary(final OWLOntology ontology, ContextDefinition context) { try { + this.nameGenerator = new JavaNameGenerator(new PrefixMap(ontology.getOWLOntologyManager(), configuration)); final JCodeModel cm = new JCodeModel(); this.voc = createVocabularyClass(cm); generateVocabulary(ontology, cm, context); @@ -266,6 +181,170 @@ public ObjectModel generateVocabulary(final OWLOntology ontology, ContextDefinit } } + private void generateVocabulary(final OWLOntology o, final JCodeModel cm, ContextDefinition context) { + final Collection col = new LinkedHashSet<>(); + col.add(o.getOWLOntologyManager().getOWLDataFactory().getOWLThing()); + col.addAll(context.classes); + col.addAll(context.objectProperties); + col.addAll(context.dataProperties); + col.addAll(context.annotationProperties); + col.addAll(context.individuals); + + generateOntologyIrisConstants(o.getOWLOntologyManager()); + + final Set visitedProperties = new HashSet<>(col.size()); + + for (final OWLEntity c : col) { + final Optional sFieldName = generateFieldName(c, o.getOWLOntologyManager(), visitedProperties); + if (sFieldName.isEmpty()) { + continue; + } + + final JFieldVar fv1 = voc.field(JMod.PUBLIC | JMod.STATIC + | JMod.FINAL, String.class, sFieldName.get(), JExpr.lit(c.getIRI().toString())); + if (configuration.shouldGenerateOwlapiIris()) { + voc.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, IRI.class, + sFieldName.get().substring(PREFIX_STRING.length()), + cm.ref(IRI.class).staticInvoke("create").arg(fv1)); + } + generateJavadoc(o, c, fv1); + entities.put(c, voc.staticRef(fv1)); + } + } + + private void generateOntologyIrisConstants(OWLOntologyManager ontologyManager) { + // Get only unique ontology IRIs sorted + final List ontologyIris = ontologyManager.ontologies().map(o -> o.getOntologyID().getOntologyIRI()) + .filter(Optional::isPresent).map(Optional::get).distinct() + .sorted(Comparator.comparing(IRI::getIRIString)) + .collect(Collectors.toList()); + ontologyIris.forEach(iri -> { + final String fieldName = ensureVocabularyItemUniqueIdentifier("ONTOLOGY_IRI_" + JavaNameGenerator.makeNameValidJava(nameGenerator.getOntologyPrefix(iri) + .orElseGet(() -> nameGenerator.generateJavaNameForIri(iri))) + .toUpperCase()); + voc.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, String.class, fieldName, JExpr.lit(iri.toString())); + }); + } + + private Optional generateFieldName(OWLEntity c, OWLOntologyManager ontologyManager, + Set visitedProperties) { + final Optional prefix = resolveFieldPrefix(c, visitedProperties); + if (prefix.isEmpty()) { + return prefix; + } + final Optional containingOntology = resolveContainingOntology(c, ontologyManager); + String fieldName = PREFIX_STRING + prefix.get() + nameGenerator.generateJavaNameForIri(c.getIRI()); + if (voc.fields().containsKey(fieldName) || ( + containingOntology.isPresent() && isPrefixedVersionRequired(containingOntology.get() + .getOntologyID()))) { + fieldName = PREFIX_STRING + prefix.get() + nameGenerator.generatePrefixedJavaNameForIri(c.getIRI(), containingOntology.get() + .getOntologyID()); + } + return Optional.of(ensureVocabularyItemUniqueIdentifier(fieldName)); + } + + private static Optional resolveContainingOntology(OWLEntity c, OWLOntologyManager ontologyManager) { + return ontologyManager.getOntologies().stream() + .filter(o -> !o.getOntologyID().isAnonymous()) + .filter(o -> o.containsEntityInSignature(c)) + .findFirst(); + } + + private static Optional resolveFieldPrefix(OWLEntity c, Set visitedProperties) { + if (c.isOWLClass()) { + return Optional.of(PREFIX_CLASS); + } else if (c.isOWLDatatype()) { + return Optional.of(PREFIX_DATATYPE); + } else if (c.isOWLDataProperty() || c.isOWLObjectProperty() || c.isOWLAnnotationProperty()) { + if (visitedProperties.contains(c.getIRI())) { + LOG.debug("Property with IRI {} already processed. Skipping.", c.getIRI()); + return Optional.empty(); + } + visitedProperties.add(c.getIRI()); + return Optional.of(PREFIX_PROPERTY); + } else if (c.isOWLNamedIndividual()) { + return Optional.of(PREFIX_INDIVIDUAL); + } + return Optional.of(""); + } + + private String ensureVocabularyItemUniqueIdentifier(String id) { + final StringBuilder sb = new StringBuilder(id); + while (voc.fields().containsKey(sb.toString())) { + sb.append(DISAMBIGUATION_SUFFIX); + } + return sb.toString(); + } + + /** + * Generates Javadoc from rdfs:comment annotation (if present). + * + * @param ontology Ontology from which the model/vocabulary is being generated + * @param owlEntity Annotated entity + * @param javaElem Element to document with Javadoc + * @return Whether the javadoc comment has been generated + */ + private boolean generateJavadoc(OWLOntology ontology, OWLEntity owlEntity, JDocCommentable javaElem) { + if (!configuration.shouldGenerateJavadoc()) { + return false; + } + final List comments = EntitySearcher.getAnnotations(owlEntity, ontology) + .filter(a -> a.getProperty().isComment() && a.getValue() + .isLiteral()) + .collect(Collectors.toList()); + final Optional langComment = comments.stream().filter(a -> a.getValue().asLiteral() + .map(l -> l.hasLang(LANGUAGE)) + .orElse(false)).findFirst(); + // First try finding a comment with a matching language tag + if (langComment.isPresent()) { + langComment.flatMap(a -> a.getValue().asLiteral()) + .ifPresent(lit -> javaElem.javadoc().add(lit.getLiteral())); + return true; + } + // If there is none such, just use the first available one + if (!comments.isEmpty()) { + OWLAnnotation anyComment = comments.get(0); + anyComment.getValue().asLiteral().ifPresent(lit -> javaElem.javadoc().add(lit.getLiteral())); + return true; + } + return false; + } + + private void generateModelImpl(final OWLOntology ontology, final JCodeModel cm, + final ContextDefinition context, final String pkg) { + LOG.info("Generating model ..."); + final PropertiesType propertiesType = configuration.getPropertiesType(); + + if (configuration.shouldGenerateThing()) { + context.classes.add(ontology.getOWLOntologyManager().getOWLDataFactory().getOWLThing()); + } + + for (final OWLClass clazz : context.classes) { + LOG.info(" Generating entity class for '{}'.", clazz); + final JDefinedClass subj = ensureEntityClassExists(pkg, cm, clazz, ontology); + + + final AtomicBoolean extendClass = new AtomicBoolean(false); + context.set.getClassIntegrityConstraints(clazz).stream() + .filter(ic -> ic instanceof AtomicSubClassConstraint).forEach(ic -> { + final AtomicSubClassConstraint icc = (AtomicSubClassConstraint) ic; + subj._extends(ensureEntityClassExists(pkg, cm, icc.getSupClass(), ontology)); + extendClass.set(true); + }); + + if (!extendClass.get()) { + addCommonClassFields(cm, subj, propertiesType); + } + for (final org.semanticweb.owlapi.model.OWLObjectProperty prop : context.objectProperties) { + generateObjectProperty(ontology, cm, context, pkg, clazz, subj, prop); + } + + for (org.semanticweb.owlapi.model.OWLDataProperty prop : context.dataProperties) { + generateDataProperty(ontology, cm, context, clazz, subj, prop); + } + } + } + private void generateObjectProperty(final OWLOntology ontology, final JCodeModel cm, final ContextDefinition context, @@ -281,8 +360,8 @@ private void generateObjectProperty(final OWLOntology ontology, ); if (Card.NO != comp.getCard()) { - JClass filler = ensureCreated(pkg, cm, comp.getFiller(), ontology); - final String fieldName = validJavaIDForIRI(prop.getIRI()); + JClass filler = ensureEntityClassExists(pkg, cm, comp.getFiller(), ontology); + final String fieldName = nameGenerator.generateJavaNameForIri(prop.getIRI()); switch (comp.getCard()) { case MULTIPLE: @@ -318,6 +397,25 @@ private void generateObjectProperty(final OWLOntology ontology, } } + private static JFieldVar addField(final String name, final JDefinedClass cls, + final JType fieldType) { + String newName = name; + + int i = 0; + while (cls.fields().containsKey(newName)) { + newName = name + "" + (++i); + } + + final JFieldVar fvId = cls.field(JMod.PROTECTED, fieldType, newName); + final String fieldName = fvId.name().substring(0, 1).toUpperCase() + fvId.name().substring(1); + final JMethod mSetId = cls.method(JMod.PUBLIC, void.class, "set" + fieldName); + final JVar v = mSetId.param(fieldType, fvId.name()); + mSetId.body().assign(JExpr._this().ref(fvId), v); + final JMethod mGetId = cls.method(JMod.PUBLIC, fieldType, "get" + fieldName); + mGetId.body()._return(fvId); + return fvId; + } + private static void setParticipationConstraintCardinality(JAnnotationUse u, cz.cvut.kbss.jopa.ic.api.ParticipationConstraint ic) { if (ic.getMin() != 0) { @@ -345,7 +443,7 @@ private void generateDataProperty(final OWLOntology ontology, final JType obj = cm._ref(resolveFieldType(comp.getFiller())); - final String fieldName = validJavaIDForIRI(prop.getIRI()); + final String fieldName = nameGenerator.generateJavaNameForIri(prop.getIRI()); JFieldVar fv; @@ -385,171 +483,84 @@ private Class resolveFieldType(OWLDatatype datatype) { return cls; } - private void generateModelImpl(final OWLOntology ontology, final JCodeModel cm, - final ContextDefinition context, final String pkg) { - LOG.info("Generating model ..."); - final PropertiesType propertiesType = configuration.getPropertiesType(); - - if (configuration.shouldGenerateThing()) { - context.classes.add(ontology.getOWLOntologyManager().getOWLDataFactory().getOWLThing()); - } - - for (final OWLClass clazz : context.classes) { - LOG.info(" Generating class '{}'.", clazz); - final JDefinedClass subj = ensureCreated(pkg, cm, clazz, ontology); - - final AtomicBoolean extendClass = new AtomicBoolean(false); - context.set.getClassIntegrityConstraints(clazz).stream() - .filter(ic -> ic instanceof AtomicSubClassConstraint).forEach(ic -> { - final AtomicSubClassConstraint icc = (AtomicSubClassConstraint) ic; - subj._extends(ensureCreated(pkg, cm, icc.getSupClass(), ontology)); - extendClass.set(true); - }); - - if (!extendClass.get()) { - addCommonClassFields(cm, subj, propertiesType); - } - for (final org.semanticweb.owlapi.model.OWLObjectProperty prop : context.objectProperties) { - generateObjectProperty(ontology, cm, context, pkg, clazz, subj, prop); - } - - for (org.semanticweb.owlapi.model.OWLDataProperty prop : context.dataProperties) { - generateDataProperty(ontology, cm, context, clazz, subj, prop); - } + private JDefinedClass ensureEntityClassExists(final String pkg, final JCodeModel cm, final OWLClass clazz, + final OWLOntology ontology) { + if (!classes.containsKey(clazz)) { + classes.put(clazz, createEntityClass(pkg, cm, clazz, ontology)); } + return classes.get(clazz); } - private void generateVocabulary(final OWLOntology o, final JCodeModel cm, ContextDefinition context) { - final Collection col = new LinkedHashSet<>(); - col.add(o.getOWLOntologyManager().getOWLDataFactory().getOWLThing()); - col.addAll(context.classes); - col.addAll(context.objectProperties); - col.addAll(context.dataProperties); - col.addAll(context.annotationProperties); - col.addAll(context.individuals); + private JDefinedClass createEntityClass(final String pkg, final JCodeModel cm, final OWLClass clazz, + final OWLOntology ontology) { + JDefinedClass cls; - generateOntologyIrisConstants(o.getOWLOntologyManager()); + String name = javaClassId(ontology, clazz, pkg, cm); - final Set visitedProperties = new HashSet<>(col.size()); + try { + cls = cm._class(name); - for (final OWLEntity c : col) { - final Optional prefix = resolveFieldPrefix(c, visitedProperties); - if (prefix.isEmpty()) { - continue; - } - final String sFieldName = ensureVocabularyItemUniqueIdentifier( - PREFIX_STRING + prefix.get() + validJavaIDForIRI(c.getIRI())); + cls.annotate(cz.cvut.kbss.jopa.model.annotations.OWLClass.class).param("iri", entities.get(clazz)); + cls._implements(Serializable.class); - final JFieldVar fv1 = voc.field(JMod.PUBLIC | JMod.STATIC - | JMod.FINAL, String.class, sFieldName, JExpr.lit(c.getIRI().toString())); - if (configuration.shouldGenerateOwlapiIris()) { - voc.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, IRI.class, - sFieldName.substring(PREFIX_STRING.length()), - cm.ref(IRI.class).staticInvoke("create").arg(fv1)); - } - generateJavadoc(o, c, fv1); - entities.put(c, voc.staticRef(fv1)); + generateClassJavadoc(ontology, clazz, cls); + } catch (JClassAlreadyExistsException e) { + LOG.trace("Class already exists. Using the existing version. {}", e.getMessage()); + cls = cm._getClass(name); } + return cls; } - private void generateOntologyIrisConstants(OWLOntologyManager ontologyManager) { - // Get only unique ontology IRIs sorted - final List ontologyIris = ontologyManager.ontologies().map(o -> o.getOntologyID().getOntologyIRI()) - .filter(Optional::isPresent).map(Optional::get).distinct() - .sorted(Comparator.comparing(IRI::getIRIString)) - .collect(Collectors.toList()); - ontologyIris.forEach(iri -> { - final String fieldName = ensureVocabularyItemUniqueIdentifier("ONTOLOGY_IRI_" + validJavaIDForIRI(iri)); - voc.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, String.class, fieldName, JExpr.lit(iri.toString())); - }); - } + private String javaClassId(OWLOntology rootOntology, OWLClass owlClass, String pkg, + JCodeModel codeModel) { + final Optional containingOntology = resolveContainingOntology(owlClass, rootOntology.getOWLOntologyManager()); + final OWLOntology onto = containingOntology.orElse(rootOntology); + String className = resolveExplicitClassName(rootOntology, owlClass) + .orElseGet(() -> JavaNameGenerator.toCamelCaseNotation(nameGenerator.generateJavaNameForIri(owlClass.getIRI()))); - private static Optional resolveFieldPrefix(OWLEntity c, Set visitedProperties) { - if (c.isOWLClass()) { - return Optional.of(PREFIX_CLASS); - } else if (c.isOWLDatatype()) { - return Optional.of(PREFIX_DATATYPE); - } else if (c.isOWLDataProperty() || c.isOWLObjectProperty() || c.isOWLAnnotationProperty()) { - if (visitedProperties.contains(c.getIRI())) { - LOG.debug("Property with IRI {} already processed. Skipping.", c.getIRI()); - return Optional.empty(); - } - visitedProperties.add(c.getIRI()); - return Optional.of(PREFIX_PROPERTY); - } else if (c.isOWLNamedIndividual()) { - return Optional.of(PREFIX_INDIVIDUAL); + if (isClassNameUnique(pkg, className, codeModel) && !isPrefixedVersionRequired(onto.getOntologyID())) { + return fqn(pkg, className); } - return Optional.of(""); - } - - private String ensureVocabularyItemUniqueIdentifier(String id) { - final StringBuilder sb = new StringBuilder(id); - while (voc.fields().containsKey(sb.toString())) { - sb.append(DISAMBIGUATION_SUFFIX); + className = JavaNameGenerator.toCamelCaseNotation(nameGenerator.generatePrefixedJavaNameForIri(owlClass.getIRI(), onto.getOntologyID())); + while (!isClassNameUnique(pkg, className, codeModel)) { + className += DISAMBIGUATION_SUFFIX; } - return sb.toString(); + return fqn(pkg, className); } - /** - * Generates Javadoc from rdfs:comment annotation (if present). - * - * @param ontology Ontology from which the model/vocabulary is being generated - * @param owlEntity Annotated entity - * @param javaElem Element to document with Javadoc - * @return Whether the javadoc comment has been generated - */ - private boolean generateJavadoc(OWLOntology ontology, OWLEntity owlEntity, JDocCommentable javaElem) { - if (!configuration.shouldGenerateJavadoc()) { - return false; - } - final List comments = EntitySearcher.getAnnotations(owlEntity, ontology) - .filter(a -> a.getProperty().isComment() && a.getValue() - .isLiteral()) - .collect(Collectors.toList()); - final Optional langComment = comments.stream().filter(a -> a.getValue().asLiteral() - .map(l -> l.hasLang(LANGUAGE)) - .orElse(false)).findFirst(); - // First try finding a comment with a matching language tag - if (langComment.isPresent()) { - langComment.flatMap(a -> a.getValue().asLiteral()) - .ifPresent(lit -> javaElem.javadoc().add(lit.getLiteral())); - return true; - } - // If there is none such, just use the first available one - if (!comments.isEmpty()) { - OWLAnnotation anyComment = comments.get(0); - anyComment.getValue().asLiteral().ifPresent(lit -> javaElem.javadoc().add(lit.getLiteral())); - return true; - } - return false; + private Optional resolveExplicitClassName(OWLOntology ontology, OWLClass owlClass) { + return EntitySearcher.getAnnotations(owlClass, ontology) + .filter(a -> isJavaClassNameAnnotation(a) && + a.getValue().isLiteral()).findFirst() + .map(a -> a.getValue().asLiteral().get().getLiteral()); } - private JDefinedClass create(final String pkg, final JCodeModel cm, final OWLClass clazz, - final OWLOntology ontology) { - JDefinedClass cls; - - String name = generateUniqueClassName(pkg, javaClassId(ontology, clazz), cm); + private boolean isJavaClassNameAnnotation(OWLAnnotation a) { + final String classNameProperty = (String) configuration.getCliParams() + .valueOf(Option.JAVA_CLASSNAME_ANNOTATION.arg); + return a.getProperty().getIRI() + .equals(IRI.create(classNameProperty != null ? classNameProperty : Constants.P_CLASS_NAME)); + } - try { - cls = cm._class(name); + private static boolean isClassNameUnique(String pkg, String simpleName, JCodeModel cm) { + return cm._getClass(fqn(pkg, simpleName)) == null; + } - cls.annotate(cz.cvut.kbss.jopa.model.annotations.OWLClass.class).param("iri", entities.get(clazz)); - cls._implements(Serializable.class); + private static String fqn(String pkg, String simpleName) { + return pkg + PACKAGE_SEPARATOR + simpleName; + } - generateClassJavadoc(ontology, clazz, cls); - } catch (JClassAlreadyExistsException e) { - LOG.trace("Class already exists. Using the existing version. {}", e.getMessage()); - cls = cm._getClass(name); - } - return cls; + private boolean isPrefixedVersionRequired(OWLOntologyID ontologyId) { + return configuration.shouldAlwaysUseOntologyPrefix() + && (ontologyId.isAnonymous() || nameGenerator.hasPrefix(ontologyId.getOntologyIRI().get())); } - private String generateUniqueClassName(String pkg, String simpleName, JCodeModel cm) { - String fqn = pkg + PACKAGE_SEPARATOR + simpleName; - while (cm._getClass(fqn) != null) { - fqn += DISAMBIGUATION_SUFFIX; + private void generateClassJavadoc(OWLOntology ontology, OWLEntity owlEntity, JDocCommentable javaElem) { + final boolean generated = generateJavadoc(ontology, owlEntity, javaElem); + if (generated) { + javaElem.javadoc().add("\n\n"); } - return fqn; + generateAuthorshipDoc(javaElem); } /** @@ -592,25 +603,6 @@ private void addCommonClassFields(final JCodeModel cm, final JDefinedClass cls, generateToStringMethod(cls, fvId, fvLabel); } - private String javaClassId(OWLOntology ontology, OWLClass owlClass) { - final Optional res = EntitySearcher.getAnnotations(owlClass, ontology) - .filter(a -> isJavaClassNameAnnotation(a) && - a.getValue().isLiteral()).findFirst(); - if (res.isPresent()) { - return res.get().getValue().asLiteral().get().getLiteral(); - } else { - return toJavaNotation(validJavaIDForIRI(owlClass.getIRI())); - } - } - - private void generateClassJavadoc(OWLOntology ontology, OWLEntity owlEntity, JDocCommentable javaElem) { - final boolean generated = generateJavadoc(ontology, owlEntity, javaElem); - if (generated) { - javaElem.javadoc().add("\n\n"); - } - generateAuthorshipDoc(javaElem); - } - private static void generateToStringMethod(JDefinedClass cls, JFieldVar idField, JFieldVar labelField) { final JMethod toString = cls.method(JMod.PUBLIC, String.class, "toString"); toString.annotate(Override.class); @@ -624,35 +616,4 @@ private static void generateToStringMethod(JDefinedClass cls, JFieldVar idField, body._return(expression); } - - private JDefinedClass ensureCreated(final String pkg, final JCodeModel cm, final OWLClass clazz, - final OWLOntology ontology) { - if (!classes.containsKey(clazz)) { - classes.put(clazz, create(pkg, cm, clazz, ontology)); - } - return classes.get(clazz); - } - - private boolean isJavaClassNameAnnotation(OWLAnnotation a) { - final String classNameProperty = (String) configuration.getCliParams() - .valueOf(Option.JAVA_CLASSNAME_ANNOTATION.arg); - return a.getProperty().getIRI() - .equals(IRI.create(classNameProperty != null ? classNameProperty : Constants.P_CLASS_NAME)); - } - - /** - * Converts a class name to the Java camel case notation - * - * @param className Generated class name - * @return Converted class name - */ - private static String toJavaNotation(String className) { - StringBuilder result = new StringBuilder(); - for (String w : className.split("_")) { - if (!w.isEmpty()) { - result.append(w.substring(0, 1).toUpperCase()).append(w.substring(1)); - } - } - return result.toString(); - } } diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/OWL2Java.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/OWL2Java.java index a2dda2a69..74c7a3a2b 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/OWL2Java.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/OWL2Java.java @@ -142,7 +142,7 @@ private static boolean invalidArgumentCount(CliParams input) { } private static void transformOwlToJava(CliParams input) { - boolean whole = input.is(Option.WHOLE_ONTOLOGY_AS_IC.arg); + boolean whole = input.is(Option.WHOLE_ONTOLOGY_AS_IC.arg, Defaults.WHOLE_ONTOLOGY_AS_IC); if (!whole && invalidTransformationOptions(input)) { return; @@ -169,7 +169,7 @@ private static boolean invalidTransformationOptions(CliParams input) { } private static void generateVocabulary(CliParams input) { - final boolean whole = input.is(Option.WHOLE_ONTOLOGY_AS_IC.arg); + final boolean whole = input.is(Option.WHOLE_ONTOLOGY_AS_IC.arg, Defaults.WHOLE_ONTOLOGY_AS_IC); if (!whole && invalidTransformationOptions(input)) { return; } diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTransformer.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTransformer.java index 12b7bc502..1c8884101 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTransformer.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTransformer.java @@ -86,7 +86,7 @@ private OWLOntology getWholeOntology(final String owlOntologyName, final String try { m.loadOntology(IRI.create(owlOntologyName)); - return new OWLOntologyMerger(m).createMergedOntology(m, IRI.create(owlOntologyName + "-generated")); + return new OWLOntologyMerger(m).createMergedOntology(m, null); } catch (OWLException | OWLRuntimeException e) { LOG.error(e.getMessage(), e); throw new OWL2JavaException("Unable to load ontology " + owlOntologyName, e); diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/cli/CommandParserProvider.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/cli/CommandParserProvider.java index 0fc338b46..aed0f1e1d 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/cli/CommandParserProvider.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/cli/CommandParserProvider.java @@ -56,6 +56,7 @@ private static ParamOptionParser initParserWithCommonOptions() { p.accepts(WHOLE_ONTOLOGY_AS_IC).withOptionalArg().ofType(Boolean.class).defaultsTo(true); p.accepts(IGNORE_FAILED_IMPORTS).withOptionalArg().ofType(Boolean.class).defaultsTo(true); p.accepts(GENERATE_JAVADOC_FROM_COMMENT).withOptionalArg().ofType(Boolean.class).defaultsTo(true); + p.accepts(ONTOLOGY_PREFIX_PROPERTY).withOptionalArg().ofType(String.class).defaultsTo(Defaults.ONTOLOGY_PREFIX_PROPERTY); return p; } diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/cli/Option.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/cli/Option.java index b0baf0901..04f96b9a1 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/cli/Option.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/cli/Option.java @@ -99,14 +99,30 @@ public enum Option { GENERATE_JAVADOC_FROM_COMMENT("doc", "generate Javadoc from rdfs:comment annotations"), /** - * Whether to automatically generate name ({@literal rdfs:label}) and description ({@literal dc:description}) fields. + * Whether to automatically generate name ({@literal rdfs:label}) and description ({@literal dc:description}) + * fields. */ GENERATE_ANNOTATION_FIELDS("ann", "automatically generate rdfs:label and dc:description attributes for all entities"), /** * Whether to automatically generate an entity class corresponding to {@literal owl:Thing}. */ - GENERATE_THING("thing", "automatically generate an entity class corresponding to owl:Thing"); + GENERATE_THING("thing", "automatically generate an entity class corresponding to owl:Thing"), + + /** + * Property whose value represents the ontology IRI prefix. + */ + ONTOLOGY_PREFIX_PROPERTY("prefixProperty", "identifier of the property whose value represents the ontology IRI prefix"), + + /** + * Specifies whether to always use ontology prefix (if available) when generating vocabulary and model. + */ + ALWAYS_USE_ONTOLOGY_PREFIX("usePrefixes", "whether to always use ontology prefix for generating vocabulary and model"), + + /** + * Specifies file containing ontology prefix mapping. + */ + PREFIX_MAPPING_FILE("prefixFile", "file containing prefix mapping"); public final String arg; final String description; diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/config/Defaults.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/config/Defaults.java index 45d6976d3..bd892a0ae 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/config/Defaults.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/config/Defaults.java @@ -81,6 +81,16 @@ public class Defaults { */ public static final boolean GENERATE_THING = true; + /** + * @see Option#ONTOLOGY_PREFIX_PROPERTY + */ + public static final String ONTOLOGY_PREFIX_PROPERTY = "http://purl.org/vocab/vann/preferredNamespacePrefix"; + + /** + * @see Option#ALWAYS_USE_ONTOLOGY_PREFIX + */ + public static final boolean ALWAYS_USE_ONTOLOGY_PREFIX = true; + private Defaults() { throw new AssertionError(); } diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/config/TransformationConfiguration.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/config/TransformationConfiguration.java index a404f418c..d4d1a0973 100644 --- a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/config/TransformationConfiguration.java +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/config/TransformationConfiguration.java @@ -20,6 +20,8 @@ import cz.cvut.kbss.jopa.owl2java.cli.CliParams; import cz.cvut.kbss.jopa.owl2java.cli.Option; import cz.cvut.kbss.jopa.owl2java.cli.PropertiesType; +import cz.cvut.kbss.jopa.owl2java.prefix.PrefixCcRemotePrefixResolver; +import cz.cvut.kbss.jopa.owl2java.prefix.RemotePrefixResolver; public class TransformationConfiguration { @@ -41,8 +43,16 @@ public class TransformationConfiguration { private final boolean generateThing; + private final String ontologyPrefixProperty; + + private final boolean alwaysUseOntologyPrefix; + + private final String prefixMappingFile; + private final CliParams cliParams; + private final RemotePrefixResolver remotePrefixResolver; + private TransformationConfiguration(TransformationConfigurationBuilder builder) { this.context = builder.context; this.packageName = builder.packageName; @@ -53,6 +63,10 @@ private TransformationConfiguration(TransformationConfigurationBuilder builder) this.propertiesType = builder.propertiesType; this.generateAnnotationFields = builder.generateAnnotationFields; this.generateThing = builder.generateThing; + this.ontologyPrefixProperty = builder.ontologyPrefixProperty; + this.alwaysUseOntologyPrefix = builder.alwaysUseOntologyPrefix; + this.prefixMappingFile = builder.prefixMappingFile; + this.remotePrefixResolver = builder.remotePrefixResolver; this.cliParams = CliParams.empty(); } @@ -68,6 +82,12 @@ private TransformationConfiguration(CliParams cliParams) { this.propertiesType = PropertiesType.fromParam(cliParams.valueOf(Option.PROPERTIES_TYPE.arg)); this.generateAnnotationFields = cliParams.is(Option.GENERATE_ANNOTATION_FIELDS.arg, Defaults.GENERATE_ANNOTATION_FIELDS); this.generateThing = cliParams.is(Option.GENERATE_THING.arg, Defaults.GENERATE_THING); + this.ontologyPrefixProperty = cliParams.has(Option.ONTOLOGY_PREFIX_PROPERTY.arg) ? cliParams.valueOf(Option.ONTOLOGY_PREFIX_PROPERTY.arg) + .toString() : Defaults.ONTOLOGY_PREFIX_PROPERTY; + this.alwaysUseOntologyPrefix = cliParams.is(Option.ALWAYS_USE_ONTOLOGY_PREFIX.arg, Defaults.ALWAYS_USE_ONTOLOGY_PREFIX); + this.prefixMappingFile = cliParams.has(Option.PREFIX_MAPPING_FILE.arg) ? cliParams.valueOf(Option.PREFIX_MAPPING_FILE.arg) + .toString() : null; + this.remotePrefixResolver = new PrefixCcRemotePrefixResolver(); } public String getContext() { @@ -110,6 +130,22 @@ public boolean shouldGenerateThing() { return generateThing; } + public String getOntologyPrefixProperty() { + return ontologyPrefixProperty; + } + + public boolean shouldAlwaysUseOntologyPrefix() { + return alwaysUseOntologyPrefix; + } + + public String getPrefixMappingFile() { + return prefixMappingFile; + } + + public RemotePrefixResolver getRemotePrefixResolver() { + return remotePrefixResolver; + } + public CliParams getCliParams() { return cliParams; } @@ -132,6 +168,10 @@ public static class TransformationConfigurationBuilder { private boolean preferMultilingualStrings = Defaults.PREFER_MULTILINGUAL_STRINGS; private boolean generateAnnotationFields = Defaults.GENERATE_ANNOTATION_FIELDS; private boolean generateThing = Defaults.GENERATE_THING; + private String ontologyPrefixProperty = Defaults.ONTOLOGY_PREFIX_PROPERTY; + private boolean alwaysUseOntologyPrefix = Defaults.ALWAYS_USE_ONTOLOGY_PREFIX; + private String prefixMappingFile = null; + private RemotePrefixResolver remotePrefixResolver = new PrefixCcRemotePrefixResolver(); public TransformationConfigurationBuilder context(String context) { this.context = context; @@ -178,6 +218,28 @@ public TransformationConfigurationBuilder generateThing(boolean generateThing) { return this; } + public TransformationConfigurationBuilder ontologyPrefixProperty(String ontologyPrefixProperty) { + if (ontologyPrefixProperty != null && !ontologyPrefixProperty.isBlank()) { + this.ontologyPrefixProperty = ontologyPrefixProperty; + } + return this; + } + + public TransformationConfigurationBuilder alwaysUseOntologyPrefix(boolean alwaysUseOntologyPrefix) { + this.alwaysUseOntologyPrefix = alwaysUseOntologyPrefix; + return this; + } + + public TransformationConfigurationBuilder prefixMappingFile(String prefixMappingFile) { + this.prefixMappingFile = prefixMappingFile; + return this; + } + + public TransformationConfigurationBuilder remotePrefixResolver(RemotePrefixResolver resolver) { + this.remotePrefixResolver = resolver; + return this; + } + public TransformationConfiguration build() { return new TransformationConfiguration(this); } diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixCcRemotePrefixResolver.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixCcRemotePrefixResolver.java new file mode 100644 index 000000000..32a22808f --- /dev/null +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixCcRemotePrefixResolver.java @@ -0,0 +1,45 @@ +package cz.cvut.kbss.jopa.owl2java.prefix; + +import cz.cvut.kbss.jopa.owl2java.Constants; +import org.semanticweb.owlapi.model.IRI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Optional; + +/** + * Resolves ontology prefixes using the prefix.cc service reverse lookup. + */ +public class PrefixCcRemotePrefixResolver implements RemotePrefixResolver { + + private static final Logger LOG = LoggerFactory.getLogger(PrefixCcRemotePrefixResolver.class); + + private static final String URL = "https://prefix.cc/reverse?format=ini&uri="; + + private final HttpClient client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build(); + + @Override + public Optional resolvePrefix(IRI ontologyIri) { + LOG.trace("Attempting to resolve prefix for IRI <{}> via prefix.cc", ontologyIri); + try { + final HttpRequest req = HttpRequest.newBuilder(URI.create(URL + ontologyIri.getIRIString())).GET() + .timeout(Constants.PREFIX_RESOLVE_TIMEOUT).build(); + final HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString()); + if (resp.statusCode() != 200) { + LOG.debug("Prefix for ontology IRI <{}> not found.", ontologyIri); + return Optional.empty(); + } + final String[] parts = resp.body().split("="); + assert parts.length == 2; + return Optional.of(parts[0]); + } catch (IOException | InterruptedException e) { + LOG.error("Unable to resolve prefix for ontology IRI <{}>.", ontologyIri, e); + return Optional.empty(); + } + } +} diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixMap.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixMap.java new file mode 100644 index 000000000..bbc667dc5 --- /dev/null +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixMap.java @@ -0,0 +1,153 @@ +package cz.cvut.kbss.jopa.owl2java.prefix; + +import cz.cvut.kbss.jopa.owl2java.config.TransformationConfiguration; +import cz.cvut.kbss.jopa.owl2java.exception.OWL2JavaException; +import cz.cvut.kbss.jopa.vocabulary.DC; +import cz.cvut.kbss.jopa.vocabulary.OWL; +import cz.cvut.kbss.jopa.vocabulary.RDF; +import cz.cvut.kbss.jopa.vocabulary.RDFS; +import cz.cvut.kbss.jopa.vocabulary.SKOS; +import org.semanticweb.owlapi.model.AxiomType; +import org.semanticweb.owlapi.model.IRI; +import org.semanticweb.owlapi.model.OWLAnnotationProperty; +import org.semanticweb.owlapi.model.OWLDataFactory; +import org.semanticweb.owlapi.model.OWLDataProperty; +import org.semanticweb.owlapi.model.OWLOntology; +import org.semanticweb.owlapi.model.OWLOntologyManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Keeps a map of prefixes resolved from provided ontologies. + *

    + * Prefixes are resolved using the following strategy: + *

      + *
    • If an ontology has an explicitly specified prefix (using the configured prefix property), it is used
    • + *
    • + *
    + */ +public class PrefixMap { + + private static final Logger LOG = LoggerFactory.getLogger(PrefixMap.class); + + /** + * Map ontology IRI -> prefix + */ + private final Map prefixes; + + public PrefixMap(OWLOntologyManager ontologyManager, TransformationConfiguration config) { + this.prefixes = resolvePrefixes(Objects.requireNonNull(ontologyManager), config); + } + + /** + * Resolves prefixes of ontologies accessible from the specified ontology manager. + * + * @param ontologyManager Manager of ontologies to process + */ + private Map resolvePrefixes(OWLOntologyManager ontologyManager, + TransformationConfiguration config) { + final Map result = new ConcurrentHashMap<>(builtInPrefixes()); + ontologyManager.ontologies().parallel().filter(o -> o.getOntologyID().isNamed()).forEach(o -> { + assert o.getOntologyID().getOntologyIRI().isPresent(); + result.putAll(resolveOntologyPrefixes(o, config.getOntologyPrefixProperty(), config.getRemotePrefixResolver())); + }); + result.putAll(resolvePrefixesFromPrefixMappingFile(config.getPrefixMappingFile())); + LOG.debug("Resolved prefix map: {}", result); + return result; + } + + private Map resolveOntologyPrefixes(OWLOntology ontology, String prefixProperty, + RemotePrefixResolver remotePrefixResolver) { + final Map result = new HashMap<>(); + final OWLDataFactory df = ontology.getOWLOntologyManager().getOWLDataFactory(); + final OWLAnnotationProperty annProperty = df.getOWLAnnotationProperty(prefixProperty); + assert ontology.getOntologyID().getOntologyIRI().isPresent(); + final IRI ontologyIri = ontology.getOntologyID().getOntologyIRI().get(); + ontology.axioms(AxiomType.ANNOTATION_ASSERTION) + .filter(ax -> ax.getProperty().equals(annProperty) && ax.getValue().isLiteral() && ax.getSubject() + .isIRI()) + .forEach(ax -> { + assert ax.getSubject().asIRI().isPresent(); + assert ax.getValue().asLiteral().isPresent(); + + result.put(ax.getSubject().asIRI().get().getIRIString(), ax.getValue().asLiteral().get() + .getLiteral()); + }); + final OWLDataProperty dataProperty = df.getOWLDataProperty(prefixProperty); + ontology.axioms(AxiomType.DATA_PROPERTY_ASSERTION) + .filter(ax -> ax.getProperty().equals(dataProperty) && ax.getSubject().isIndividual()) + .forEach(ax -> result.put(ax.getSubject().asOWLNamedIndividual().toStringID(), ax.getObject() + .getLiteral())); + if (!result.containsKey(ontologyIri.getIRIString())) { + remotePrefixResolver.resolvePrefix(ontologyIri).ifPresent(p -> result.put(ontologyIri.getIRIString(), p)); + } + return result; + } + + private Map resolvePrefixesFromPrefixMappingFile(String mappingFilePath) { + if (mappingFilePath == null) { + return Collections.emptyMap(); + } + final File file = new File(mappingFilePath); + try { + LOG.debug("Loading prefix mapping from file '{}'.", file); + final List lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); + final Map prefixMap = new HashMap<>(lines.size()); + lines.stream().filter(line -> !line.isBlank()).forEach(line -> { + final String[] mapping = line.split("="); + if (mapping.length != 2) { + LOG.warn("Mapping line '{}' does not correspond to the expected pattern '$namespace=$prefix'. Skipping it.", line); + } + prefixMap.put(mapping[0], mapping[1]); + }); + return prefixMap; + } catch (IOException e) { + LOG.error("Unable to read prefix mapping file.", e); + throw new OWL2JavaException("Unable to read prefix mapping file.", e); + } + } + + /** + * Gets prefix for an ontology with the specified IRI. + * + * @param ontologyIri Ontology IRI + * @return Resolved prefix, if available + */ + public Optional getPrefix(IRI ontologyIri) { + Objects.requireNonNull(ontologyIri); + return Optional.ofNullable(prefixes.get(ontologyIri.getIRIString())); + } + + /** + * Checks whether a prefix has been resolved for the specified ontology IRI. + * + * @param ontologyIri Ontology IRI + * @return {@code true} if a prefix is registered for the ontology IRI, {@code false} otherwise + */ + public boolean hasPrefix(IRI ontologyIri) { + Objects.requireNonNull(ontologyIri); + return prefixes.containsKey(ontologyIri.getIRIString()); + } + + private static Map builtInPrefixes() { + return Map.of( + RDF.NAMESPACE, RDF.PREFIX, + RDFS.NAMESPACE, RDFS.PREFIX, + OWL.NAMESPACE, OWL.PREFIX, + SKOS.NAMESPACE, SKOS.PREFIX, + DC.Terms.NAMESPACE, "dcterms" + ); + } +} diff --git a/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/prefix/RemotePrefixResolver.java b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/prefix/RemotePrefixResolver.java new file mode 100644 index 000000000..c30e46710 --- /dev/null +++ b/jopa-owl2java/src/main/java/cz/cvut/kbss/jopa/owl2java/prefix/RemotePrefixResolver.java @@ -0,0 +1,19 @@ +package cz.cvut.kbss.jopa.owl2java.prefix; + +import org.semanticweb.owlapi.model.IRI; + +import java.util.Optional; + +/** + * Attempts to resolve ontology prefix by invoking a remote service. + */ +public interface RemotePrefixResolver { + + /** + * Resolves prefix of an ontology with the specified IRI. + * + * @param ontologyIri IRI of the ontology to get prefix for + * @return Resolved prefix wrapped in {@code Optional}, empty {@code Optional} if unable to get the prefix + */ + Optional resolvePrefix(IRI ontologyIri); +} diff --git a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/JavaNameGeneratorTest.java b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/JavaNameGeneratorTest.java new file mode 100644 index 000000000..f9db6e40e --- /dev/null +++ b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/JavaNameGeneratorTest.java @@ -0,0 +1,96 @@ +package cz.cvut.kbss.jopa.owl2java; + +import cz.cvut.kbss.jopa.owl2java.prefix.PrefixMap; +import cz.cvut.kbss.jopa.vocabulary.RDFS; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.semanticweb.owlapi.model.IRI; +import org.semanticweb.owlapi.model.OWLOntologyID; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class JavaNameGeneratorTest { + + private static final String PREFIX = "http://onto.fel.cvut.cz/ontologies/owl2java-test/"; + + @Mock + private PrefixMap prefixMap; + + @InjectMocks + private JavaNameGenerator sut; + + @Test + void toCamelCaseNotationCamelCasesStringsSeparatedByUnderscore() { + assertEquals("TestValueCamelCased", JavaNameGenerator.toCamelCaseNotation("test_value_camel_cased")); + } + + @Test + void generateJavaNameForIriGeneratesValidJavaIdentifiersForIriWithNonAsciiCharacters() { + final IRI iri = IRI.create(PREFIX + "navržený-pojem"); + assertEquals("navrzeny_pojem", sut.generateJavaNameForIri(iri)); + } + + @Test + void generateJavaNameForIriSanitizesJavaKeywords() { + final IRI iri = IRI.create(PREFIX + "volatile"); + assertEquals("_volatile", sut.generateJavaNameForIri(iri)); + } + + @Test + void generateJavaNameForIriUsesFragmentWhenIriContainsIt() { + final IRI iri = IRI.create("http://onto.fel.cvut.cz/ontologies/owl2java-test#Fragment"); + assertEquals("Fragment", sut.generateJavaNameForIri(iri)); + } + + @Test + void generateJavaNameForIriUsesPartBetweenLastHashAndFragmentWhenFragmentIsLastChar() { + final IRI iri = IRI.create("http://www.w3.org/ns/activitystreams#"); + assertEquals("activitystreams", sut.generateJavaNameForIri(iri)); + } + + @Test + void generateJavaNameForIriUsesPartBetweenLastAndSecondToLastSlashWhenSlashIsLastChar() { + final IRI iri = IRI.create("http://purl.org/vocab/vann/"); + assertEquals("vann", sut.generateJavaNameForIri(iri)); + } + + @Test + void generatePrefixedJavaNameForIriUsesPrefixRegisteredForOntologyIri() { + final IRI ontologyIri = IRI.create(RDFS.NAMESPACE); + when(prefixMap.getPrefix(ontologyIri)).thenReturn(Optional.of(RDFS.PREFIX)); + assertEquals("rdfs_label", sut.generatePrefixedJavaNameForIri(IRI.create(RDFS.LABEL), new OWLOntologyID(ontologyIri))); + } + + @Test + void generatePrefixedJavaNameForIriResolvesPrefixFromOntologyIriWhenNoPrefixIsRegisteredInPrefixMap() { + final IRI ontologyIri = IRI.create("http://www.w3.org/ns/activitystreams#"); + final IRI iri = IRI.create("https://www.w3.org/ns/activitystreams#Event"); + when(prefixMap.getPrefix(any())).thenReturn(Optional.empty()); + assertEquals("activitystreams_Event", sut.generatePrefixedJavaNameForIri(iri, new OWLOntologyID(ontologyIri))); + } + + @Test + void generatePrefixedJavaNameForIriReturnsNameWithoutPrefixWhenOntologyIdIsAnonymous() { + final IRI iri = IRI.create("https://www.w3.org/ns/activitystreams#Event"); + assertEquals("Event", sut.generatePrefixedJavaNameForIri(iri, new OWLOntologyID())); + verify(prefixMap, never()).getPrefix(any()); + } + + @Test + void generatedPrefixedJavaNameForIriReturnsValidJavaNameWhenPrefixContainsDashes() { + final IRI ontologyIri = IRI.create("http://onto.fel.cvut.cz/ontologies/slovn\\u00edk/agendov\\u00fd/popis-dat"); + final IRI iri = IRI.create("http://onto.fel.cvut.cz/ontologies/slovn\\u00edk/agendov\\u00fd/popis-dat/pojem/atribut"); + when(prefixMap.getPrefix(ontologyIri)).thenReturn(Optional.of("popis-dat")); + assertEquals("popis_dat_atribut", sut.generatePrefixedJavaNameForIri(iri, new OWLOntologyID(ontologyIri))); + } +} diff --git a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/JavaTransformerTest.java b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/JavaTransformerTest.java index 823ad362e..965604636 100644 --- a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/JavaTransformerTest.java +++ b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/JavaTransformerTest.java @@ -21,6 +21,7 @@ import com.sun.codemodel.JFieldVar; import com.sun.codemodel.JType; import cz.cvut.kbss.jopa.model.MultilingualString; +import cz.cvut.kbss.jopa.owl2java.config.Defaults; import cz.cvut.kbss.jopa.owl2java.config.TransformationConfiguration; import cz.cvut.kbss.jopa.vocabulary.RDFS; import cz.cvut.kbss.jopa.vocabulary.SKOS; @@ -28,10 +29,12 @@ import org.junit.jupiter.api.Test; import org.semanticweb.owlapi.apibinding.OWLManager; import org.semanticweb.owlapi.model.IRI; +import org.semanticweb.owlapi.model.OWLAnnotationProperty; import org.semanticweb.owlapi.model.OWLClass; import org.semanticweb.owlapi.model.OWLDataFactory; import org.semanticweb.owlapi.model.OWLOntology; import org.semanticweb.owlapi.model.OWLOntologyManager; +import org.semanticweb.owlapi.util.OWLOntologyMerger; import org.semanticweb.owlapi.vocab.OWL2Datatype; import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl; @@ -43,6 +46,7 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasKey; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -54,7 +58,9 @@ class JavaTransformerTest { private static final String PREFIX = "http://onto.fel.cvut.cz/ontologies/jopa/"; - private static final String ONTOLOGY_IRI = "http://onto.fel.cvut.cz/ontologies/owl2java/java-transformer-test"; + private static final String ONTOLOGY_IRI = "http://onto.fel.cvut.cz/ontologies/owl2java/java-transformer-test/"; + + private OWLOntologyManager ontologyManager; private OWLOntology ontology; @@ -64,10 +70,11 @@ class JavaTransformerTest { @BeforeEach void setUp() throws Exception { - final OWLOntologyManager manager = OWLManager.createOWLOntologyManager(); - this.ontology = manager.createOntology(IRI.create(ONTOLOGY_IRI)); + this.ontologyManager = OWLManager.createOWLOntologyManager(); + this.ontology = ontologyManager.createOntology(IRI.create(ONTOLOGY_IRI)); this.dataFactory = new OWLDataFactoryImpl(); - this.sut = new JavaTransformer(TransformationConfiguration.builder().packageName("").build()); + this.sut = new JavaTransformer(TransformationConfiguration.builder().packageName("") + .alwaysUseOntologyPrefix(false).build()); } @Test @@ -187,6 +194,7 @@ private ContextDefinition generateAxiomsForLangStrings(String className, String @Test void generateModelGeneratesFieldOfTypeStringForLangStringRangeWhenConfiguredNotToPreferMultilingualStrings() { this.sut = new JavaTransformer(TransformationConfiguration.builder().preferMultilingualStrings(false) + .alwaysUseOntologyPrefix(false) .packageName("").build()); final String className = "TestClass"; final String fieldName = "multilingualString"; @@ -238,6 +246,7 @@ void generateModelGeneratesJavadocWithCommentUsingLanguageLessAnnotationValue() @Test void generateModelDoesNotGenerateLabelAndDescriptionFieldsWhenDisabled() { this.sut = new JavaTransformer(TransformationConfiguration.builder().packageName("") + .alwaysUseOntologyPrefix(false) .generateAnnotationFields(false).build()); final String className = "TestClass"; final IRI iri = IRI.create(PREFIX + className); @@ -269,7 +278,7 @@ void generateModelDoesNotGenerateThingEntityClassWhenDisabled() { @Test void generateModelDisambiguateClassesWithIdenticalJavaName() { this.sut = new JavaTransformer(TransformationConfiguration.builder().packageName("").generateThing(false) - .build()); + .alwaysUseOntologyPrefix(false).build()); final String className = "Concept"; final IRI iriOne = IRI.create(PREFIX + className); final IRI iriTwo = IRI.create(SKOS.CONCEPT); @@ -279,6 +288,7 @@ void generateModelDisambiguateClassesWithIdenticalJavaName() { context.add(dataFactory.getOWLClass(iriOne)); context.add(dataFactory.getOWLClass(iriTwo)); context.parse(); + final ObjectModel result = sut.generateModel(ontology, context); final Iterator classIterator = result.getCodeModel()._package(Constants.MODEL_PACKAGE).classes(); final List classes = new ArrayList<>(); @@ -286,6 +296,119 @@ void generateModelDisambiguateClassesWithIdenticalJavaName() { classes.add(classIterator.next()); } assertEquals(2, classes.size()); - assertEquals(2, classes.stream().filter(cls -> cls.name().startsWith(className)).count()); + assertEquals(2, classes.stream().filter(cls -> cls.name().endsWith(className)).count()); + } + + @Test + void generateVocabularyDisambiguateResourcesWithSameNameUsingPrefix() throws Exception { + final OWLAnnotationProperty prefixProperty = dataFactory.getOWLAnnotationProperty(Defaults.ONTOLOGY_PREFIX_PROPERTY); + ontology.add(dataFactory.getOWLAnnotationAssertionAxiom(prefixProperty, IRI.create(ONTOLOGY_IRI), dataFactory.getOWLLiteral("test"))); + final IRI personIri = IRI.create(PREFIX + "Person"); + ontology.add(dataFactory.getOWLDeclarationAxiom(dataFactory.getOWLClass(personIri))); + final ContextDefinition context = new ContextDefinition(); + context.add(dataFactory.getOWLClass(personIri)); + final IRI foafOntIri = IRI.create("http://xmlns.com/foaf/0.1/"); + final OWLOntology foafOnto = ontology.getOWLOntologyManager().createOntology(foafOntIri); + foafOnto.add(dataFactory.getOWLAnnotationAssertionAxiom(prefixProperty, foafOntIri, dataFactory.getOWLLiteral("foaf"))); + final IRI foafPersonIri = IRI.create(foafOntIri.getIRIString() + "Person"); + foafOnto.add(dataFactory.getOWLDeclarationAxiom(dataFactory.getOWLClass(foafPersonIri))); + context.add(dataFactory.getOWLClass(foafPersonIri)); + final OWLOntology merged = new OWLOntologyMerger(ontologyManager).createMergedOntology(ontologyManager, null); + + final ObjectModel result = sut.generateVocabulary(merged, context); + final JDefinedClass vocabClass = result.getCodeModel()._getClass(Constants.VOCABULARY_CLASS); + assertNotNull(vocabClass); + final Map fields = vocabClass.fields(); + assertThat(fields.keySet(), hasItem("s_c_Person")); + assertThat(fields.keySet(), hasItem("s_c_foaf_Person")); + } + + @Test + void generateVocabularyUsesOntologyPrefixesInFieldNamesWhenGeneratingOntologyIriConstants() throws Exception { + final ContextDefinition context = new ContextDefinition(); + final OWLAnnotationProperty prefixProperty = dataFactory.getOWLAnnotationProperty(Defaults.ONTOLOGY_PREFIX_PROPERTY); + ontology.add(dataFactory.getOWLAnnotationAssertionAxiom(prefixProperty, IRI.create(ONTOLOGY_IRI), dataFactory.getOWLLiteral("test"))); + final IRI foafOntIri = IRI.create("http://xmlns.com/foaf/0.1/"); + final OWLOntology foafOnto = ontology.getOWLOntologyManager().createOntology(foafOntIri); + foafOnto.add(dataFactory.getOWLAnnotationAssertionAxiom(prefixProperty, foafOntIri, dataFactory.getOWLLiteral("foaf"))); + final OWLOntology merged = new OWLOntologyMerger(ontologyManager).createMergedOntology(ontologyManager, null); + + final ObjectModel result = sut.generateVocabulary(merged, context); + final JDefinedClass vocabClass = result.getCodeModel()._getClass(Constants.VOCABULARY_CLASS); + assertNotNull(vocabClass); + final Map fields = vocabClass.fields(); + assertThat(fields.keySet(), hasItem("ONTOLOGY_IRI_TEST")); + assertThat(fields.keySet(), hasItem("ONTOLOGY_IRI_FOAF")); + } + + @Test + void generateModelDisambiguateClassesWithIdenticalJavaNameUsingOntologyPrefixes() throws Exception { + this.sut = new JavaTransformer(TransformationConfiguration.builder().packageName("").generateThing(false) + .build()); + final String className = "Concept"; + final IRI iriOne = IRI.create(PREFIX + className); + final IRI iriTwo = IRI.create(SKOS.CONCEPT); + ontology.add(dataFactory.getOWLDeclarationAxiom(dataFactory.getOWLClass(iriOne))); + final IRI skosOntIri = IRI.create(SKOS.NAMESPACE); + final OWLOntology skosOnto = ontology.getOWLOntologyManager().createOntology(skosOntIri); + final OWLAnnotationProperty prefixProperty = dataFactory.getOWLAnnotationProperty(Defaults.ONTOLOGY_PREFIX_PROPERTY); + skosOnto.add(dataFactory.getOWLAnnotationAssertionAxiom(prefixProperty, IRI.create(SKOS.NAMESPACE), dataFactory.getOWLLiteral(SKOS.PREFIX))); + skosOnto.add(dataFactory.getOWLDeclarationAxiom(dataFactory.getOWLClass(iriTwo))); + final OWLOntology merged = new OWLOntologyMerger(ontologyManager).createMergedOntology(ontologyManager, null); + final ContextDefinition context = new ContextDefinition(); + context.add(dataFactory.getOWLClass(iriOne)); + context.add(dataFactory.getOWLClass(iriTwo)); + context.parse(); + + final ObjectModel result = sut.generateModel(merged, context); + final Iterator classIterator = result.getCodeModel()._package(Constants.MODEL_PACKAGE).classes(); + final List classes = new ArrayList<>(); + while (classIterator.hasNext()) { + classes.add(classIterator.next()); + } + assertEquals(2, classes.size()); + assertEquals(2, classes.stream().filter(cls -> cls.name().endsWith(className)).count()); + assertTrue(classes.stream().anyMatch(cls -> cls.name().equals("Skos" + className))); + } + + @Test + void generateVocabularyAlwaysOutputsPrefixedNamesWhenConfiguredTo() { + this.sut = new JavaTransformer(TransformationConfiguration.builder().packageName("") + .alwaysUseOntologyPrefix(true).build()); + final OWLAnnotationProperty prefixProperty = dataFactory.getOWLAnnotationProperty(Defaults.ONTOLOGY_PREFIX_PROPERTY); + ontology.add(dataFactory.getOWLAnnotationAssertionAxiom(prefixProperty, IRI.create(ONTOLOGY_IRI), dataFactory.getOWLLiteral("test"))); + final IRI personIri = IRI.create(PREFIX + "Person"); + ontology.add(dataFactory.getOWLDeclarationAxiom(dataFactory.getOWLClass(personIri))); + final ContextDefinition context = new ContextDefinition(); + context.add(dataFactory.getOWLClass(personIri)); + + final ObjectModel result = sut.generateVocabulary(ontology, context); + final JDefinedClass vocabClass = result.getCodeModel()._getClass(Constants.VOCABULARY_CLASS); + assertNotNull(vocabClass); + final Map fields = vocabClass.fields(); + assertThat(fields.keySet(), hasItem("s_c_test_Person")); + } + + @Test + void generateModelAlwaysOutputsPrefixedEntityClassNamesWhenConfiguredTo() { + this.sut = new JavaTransformer(TransformationConfiguration.builder().packageName("") + .alwaysUseOntologyPrefix(true) + .build()); + final String className = "Concept"; + final IRI iriOne = IRI.create(PREFIX + className); + final OWLAnnotationProperty prefixProperty = dataFactory.getOWLAnnotationProperty(Defaults.ONTOLOGY_PREFIX_PROPERTY); + ontology.add(dataFactory.getOWLDeclarationAxiom(dataFactory.getOWLClass(iriOne))); + ontology.add(dataFactory.getOWLAnnotationAssertionAxiom(prefixProperty, IRI.create(ONTOLOGY_IRI), dataFactory.getOWLLiteral("test"))); + final ContextDefinition context = new ContextDefinition(); + context.add(dataFactory.getOWLClass(iriOne)); + context.parse(); + + final ObjectModel result = sut.generateModel(ontology, context); + final Iterator classIterator = result.getCodeModel()._package(Constants.MODEL_PACKAGE).classes(); + final List classes = new ArrayList<>(); + while (classIterator.hasNext()) { + classes.add(classIterator.next()); + } + assertTrue(classes.stream().anyMatch(cls -> cls.name().equals("Test" + className))); } } diff --git a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTest.java b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTest.java index 48d5c7bd3..f15c59927 100644 --- a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTest.java +++ b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTest.java @@ -49,7 +49,7 @@ public class OWL2JavaTest { @BeforeEach public void setUp() throws Exception { - this.mappingFilePath = TestUtils.resolveMappingFilePath(); + this.mappingFilePath = TestUtils.resolveTestResourcesFilePath(TestUtils.MAPPING_FILE_NAME); System.setOut(new PrintStream(outContent)); System.setErr(new PrintStream(errContent)); } diff --git a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTransformerTest.java b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTransformerTest.java index 8ff73d46e..79f7deee3 100644 --- a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTransformerTest.java +++ b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/OWL2JavaTransformerTest.java @@ -76,7 +76,7 @@ public class OWL2JavaTransformerTest { @BeforeEach public void setUp() throws Exception { - this.mappingFilePath = TestUtils.resolveMappingFilePath(); + this.mappingFilePath = TestUtils.resolveTestResourcesFilePath(TestUtils.MAPPING_FILE_NAME); this.dataFactory = new OWLDataFactoryImpl(); this.transformer = new OWL2JavaTransformer(); } @@ -347,8 +347,8 @@ public void generateVocabularyHandlesDuplicateLocalNameWhenAddingOntologyIri() t transformer.generateVocabulary(config(null, "", targetDir.getAbsolutePath(), true).build()); final File vocabularyFile = targetDir.listFiles()[0]; final String fileContents = readFile(vocabularyFile); - assertTrue(fileContents.contains("ONTOLOGY_IRI_model")); - assertTrue(fileContents.contains("ONTOLOGY_IRI_model_A")); + assertTrue(fileContents.contains("ONTOLOGY_IRI_MODEL")); + assertTrue(fileContents.contains("ONTOLOGY_IRI_MODEL_A")); } @Test diff --git a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/TestUtils.java b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/TestUtils.java index 4bc20cd12..37d73d507 100644 --- a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/TestUtils.java +++ b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/TestUtils.java @@ -48,8 +48,8 @@ private TestUtils() { throw new AssertionError(); } - public static String resolveMappingFilePath() throws UnsupportedEncodingException { - final URL resource = TestUtils.class.getClassLoader().getResource(MAPPING_FILE_NAME); + public static String resolveTestResourcesFilePath(String fileName) throws UnsupportedEncodingException { + final URL resource = TestUtils.class.getClassLoader().getResource(fileName); final String decodedPath = URLDecoder.decode(resource.getFile(), StandardCharsets.UTF_8.toString()); final File mf = new File(decodedPath); return mf.getAbsolutePath(); diff --git a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/environment/Generator.java b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/environment/Generator.java new file mode 100644 index 000000000..90b17e3c9 --- /dev/null +++ b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/environment/Generator.java @@ -0,0 +1,16 @@ +package cz.cvut.kbss.jopa.owl2java.environment; + +import java.net.URI; +import java.util.Random; + +public class Generator { + + + private static final Random RANDOM = new Random(); + + public static final String IRI_BASE = "https://onto.fel.cvut.cz/ontologies/jopa/owl2java/"; + + public static URI generateUri() { + return URI.create(IRI_BASE + RANDOM.nextInt()); + } +} diff --git a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixCcRemotePrefixResolverTest.java b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixCcRemotePrefixResolverTest.java new file mode 100644 index 000000000..498fda32a --- /dev/null +++ b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixCcRemotePrefixResolverTest.java @@ -0,0 +1,28 @@ +package cz.cvut.kbss.jopa.owl2java.prefix; + +import cz.cvut.kbss.jopa.owl2java.environment.Generator; +import org.junit.jupiter.api.Test; +import org.semanticweb.owlapi.model.IRI; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PrefixCcRemotePrefixResolverTest { + + private final PrefixCcRemotePrefixResolver sut = new PrefixCcRemotePrefixResolver(); + + @Test + void resolvePrefixReturnsPrefixReturnedByPrefixCcService() { + final Optional result = sut.resolvePrefix(IRI.create("http://xmlns.com/foaf/0.1/")); + assertTrue(result.isPresent()); + assertEquals("foaf", result.get()); + } + + @Test + void resolvePrefixReturnsEmptyOptionalWhenPrefixCcServiceReturnsNotFound() { + final Optional result = sut.resolvePrefix(IRI.create(Generator.generateUri())); + assertTrue(result.isEmpty()); + } +} diff --git a/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixMapTest.java b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixMapTest.java new file mode 100644 index 000000000..153a6f3b4 --- /dev/null +++ b/jopa-owl2java/src/test/java/cz/cvut/kbss/jopa/owl2java/prefix/PrefixMapTest.java @@ -0,0 +1,178 @@ +package cz.cvut.kbss.jopa.owl2java.prefix; + +import cz.cvut.kbss.jopa.owl2java.TestUtils; +import cz.cvut.kbss.jopa.owl2java.config.Defaults; +import cz.cvut.kbss.jopa.owl2java.config.TransformationConfiguration; +import cz.cvut.kbss.jopa.owl2java.environment.Generator; +import cz.cvut.kbss.jopa.owl2java.exception.OWL2JavaException; +import cz.cvut.kbss.jopa.vocabulary.DC; +import cz.cvut.kbss.jopa.vocabulary.OWL; +import cz.cvut.kbss.jopa.vocabulary.RDF; +import cz.cvut.kbss.jopa.vocabulary.RDFS; +import cz.cvut.kbss.jopa.vocabulary.SKOS; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.semanticweb.owlapi.apibinding.OWLManager; +import org.semanticweb.owlapi.model.IRI; +import org.semanticweb.owlapi.model.OWLAnnotationProperty; +import org.semanticweb.owlapi.model.OWLDataFactory; +import org.semanticweb.owlapi.model.OWLDataProperty; +import org.semanticweb.owlapi.model.OWLOntology; +import org.semanticweb.owlapi.model.OWLOntologyCreationException; +import org.semanticweb.owlapi.model.OWLOntologyManager; +import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class PrefixMapTest { + + private final OWLDataFactory dataFactory = new OWLDataFactoryImpl(); + + private final OWLOntologyManager ontologyManager = OWLManager.createOWLOntologyManager(); + + private final RemotePrefixResolver remotePrefixResolver = mock(RemotePrefixResolver.class); + + @Test + void getPrefixReturnsPrefixResolvedFromSingleStringAnnotationPropertyValue() { + final TransformationConfiguration config = configBuilder().build(); + final String prefix = "owl2java"; + final IRI ontologyIri = IRI.create(Generator.generateUri()); + assertOntologyPrefixAnnotation(prefix, ontologyIri); + final PrefixMap sut = new PrefixMap(ontologyManager, config); + final Optional result = sut.getPrefix(ontologyIri); + assertTrue(result.isPresent()); + assertEquals(prefix, result.get()); + } + + private TransformationConfiguration.TransformationConfigurationBuilder configBuilder() { + return new TransformationConfiguration.TransformationConfigurationBuilder().remotePrefixResolver(remotePrefixResolver); + } + + @Test + void getPrefixReturnsPrefixResolvedFromSingleStringDataPropertyValue() throws Exception { + final TransformationConfiguration config = configBuilder().build(); + final String prefix = "owl2java"; + final IRI ontologyIri = IRI.create(Generator.generateUri()); + final OWLOntology ontology = ontologyManager.createOntology(ontologyIri); + final OWLDataProperty prefixProperty = dataFactory.getOWLDataProperty(Defaults.ONTOLOGY_PREFIX_PROPERTY); + ontology.add(dataFactory.getOWLDataPropertyAssertionAxiom(prefixProperty, dataFactory.getOWLNamedIndividual(ontologyIri), dataFactory.getOWLLiteral(prefix))); + final PrefixMap sut = new PrefixMap(ontologyManager, config); + final Optional result = sut.getPrefix(ontologyIri); + assertTrue(result.isPresent()); + assertEquals(prefix, result.get()); + } + + @Test + void getPrefixReturnsPrefixesOfMultipleOntologies() { + final TransformationConfiguration config = configBuilder().build(); + final Map prefixes = Map.of( + "owl2java", IRI.create(Generator.generateUri()), + "jopa", IRI.create(Generator.generateUri()) + ); + prefixes.forEach(this::assertOntologyPrefixAnnotation); + final PrefixMap sut = new PrefixMap(ontologyManager, config); + + prefixes.forEach((prefix, iri) -> { + final Optional result = sut.getPrefix(iri); + assertTrue(result.isPresent()); + assertEquals(prefix, result.get()); + }); + } + + @Test + void getPrefixReturnsPrefixWhenMultiplePrefixAssertionsAreInMergedOntology() { + final TransformationConfiguration config = configBuilder().build(); + final Map prefixes = Map.of( + "owl2java", IRI.create(Generator.generateUri()), + "jopa", IRI.create(Generator.generateUri()) + ); + final OWLOntology ontology = assertOntologyPrefixAnnotation("owl2java", prefixes.get("owl2java")); + final OWLAnnotationProperty prefixProperty = dataFactory.getOWLAnnotationProperty(Defaults.ONTOLOGY_PREFIX_PROPERTY); + ontology.add(dataFactory.getOWLAnnotationAssertionAxiom(prefixProperty, prefixes.get("jopa"), dataFactory.getOWLLiteral("jopa"))); + + final PrefixMap sut = new PrefixMap(ontologyManager, config); + prefixes.forEach((prefix, iri) -> { + final Optional result = sut.getPrefix(iri); + assertTrue(result.isPresent()); + assertEquals(prefix, result.get()); + }); + } + + private OWLOntology assertOntologyPrefixAnnotation(String prefix, IRI ontologyIri) { + try { + final OWLOntology ontology = ontologyManager.createOntology(ontologyIri); + final OWLAnnotationProperty prefixProperty = dataFactory.getOWLAnnotationProperty(Defaults.ONTOLOGY_PREFIX_PROPERTY); + ontology.add(dataFactory.getOWLAnnotationAssertionAxiom(prefixProperty, ontologyIri, dataFactory.getOWLLiteral(prefix))); + return ontology; + } catch (OWLOntologyCreationException e) { + throw new RuntimeException(e); + } + } + + @ParameterizedTest + @MethodSource("predefinedPrefixes") + void getPrefixReturnsPredefinedPrefixesForSelectedVocabularies(String expectedPrefix, IRI iri) { + final TransformationConfiguration config = configBuilder().build(); + final String prefix = "owl2java"; + final IRI ontologyIri = IRI.create(Generator.generateUri()); + assertOntologyPrefixAnnotation(prefix, ontologyIri); + final PrefixMap sut = new PrefixMap(ontologyManager, config); + final Optional result = sut.getPrefix(iri); + assertTrue(result.isPresent()); + assertEquals(expectedPrefix, result.get()); + } + + static Stream predefinedPrefixes() { + return Stream.of( + Arguments.of("dcterms", IRI.create(DC.Terms.NAMESPACE)), + Arguments.of(RDF.PREFIX, IRI.create(RDF.NAMESPACE)), + Arguments.of(RDFS.PREFIX, IRI.create(RDFS.NAMESPACE)), + Arguments.of(OWL.PREFIX, IRI.create(OWL.NAMESPACE)), + Arguments.of(SKOS.PREFIX, IRI.create(SKOS.NAMESPACE)) + ); + } + + @Test + void getPrefixReturnsPrefixResolvedFromPrefixMappingFileSpecifiedInConfiguration() throws Exception { + final String prefixMappingFilePath = TestUtils.resolveTestResourcesFilePath("prefixMappingFile"); + final TransformationConfiguration config = configBuilder().prefixMappingFile(prefixMappingFilePath).build(); + final PrefixMap sut = new PrefixMap(ontologyManager, config); + final Optional siocPrefix = sut.getPrefix(IRI.create("http://rdfs.org/sioc/ns#")); + assertTrue(siocPrefix.isPresent()); + assertEquals("sioc", siocPrefix.get()); + final Optional ddoPrefix = sut.getPrefix(IRI.create("http://onto.fel.cvut.cz/ontologies/dataset-descriptor/")); + assertTrue(ddoPrefix.isPresent()); + assertEquals("ddo", ddoPrefix.get()); + } + + @Test + void prefixResolvingThrowsOWL2JavaExceptionWhenPrefixMappingFileIsNotFound() { + final TransformationConfiguration config = configBuilder().prefixMappingFile("unknownFile").build(); + assertThrows(OWL2JavaException.class, () -> new PrefixMap(ontologyManager, config)); + } + + @Test + void prefixResolvingUsesRemotePrefixResolverWhenOntologyPrefixCannotBeResolvedLocally() throws Exception { + final TransformationConfiguration config = configBuilder().build(); + final String prefix = "owl2java"; + final IRI ontologyIri = IRI.create(Generator.generateUri()); + ontologyManager.createOntology(ontologyIri); + when(remotePrefixResolver.resolvePrefix(ontologyIri)).thenReturn(Optional.of(prefix)); + final PrefixMap sut = new PrefixMap(ontologyManager, config); + final Optional result = sut.getPrefix(ontologyIri); + assertTrue(result.isPresent()); + assertEquals(prefix, result.get()); + verify(remotePrefixResolver).resolvePrefix(ontologyIri); + } +} diff --git a/jopa-owl2java/src/test/resources/prefixMappingFile b/jopa-owl2java/src/test/resources/prefixMappingFile new file mode 100644 index 000000000..43d68bed9 --- /dev/null +++ b/jopa-owl2java/src/test/resources/prefixMappingFile @@ -0,0 +1,2 @@ +http://rdfs.org/sioc/ns#=sioc +http://onto.fel.cvut.cz/ontologies/dataset-descriptor/=ddo diff --git a/jopa-owlapi-utils/pom.xml b/jopa-owlapi-utils/pom.xml index 404fdef6e..120223ac4 100644 --- a/jopa-owlapi-utils/pom.xml +++ b/jopa-owlapi-utils/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml diff --git a/jopa-owlapi-utils/src/main/java/cz/cvut/kbss/jopa/owlapi/identityreasoner/OWLAPIIdentityReasoner.java b/jopa-owlapi-utils/src/main/java/cz/cvut/kbss/jopa/owlapi/identityreasoner/OWLAPIIdentityReasoner.java deleted file mode 100644 index 40311eb6b..000000000 --- a/jopa-owlapi-utils/src/main/java/cz/cvut/kbss/jopa/owlapi/identityreasoner/OWLAPIIdentityReasoner.java +++ /dev/null @@ -1,498 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.owlapi.identityreasoner; - -import org.semanticweb.owlapi.model.*; -import org.semanticweb.owlapi.reasoner.*; -import org.semanticweb.owlapi.reasoner.impl.*; -import org.semanticweb.owlapi.search.EntitySearcher; -import org.semanticweb.owlapi.util.Version; - -import javax.annotation.Nonnull; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -@Deprecated -class OWLAPIIdentityReasoner implements OWLReasoner { - - private final OWLOntology o; - - public OWLAPIIdentityReasoner(final OWLOntology o) { - this.o = o; - } - - - public void dispose() { - // do nothing - } - - - public void flush() { - // do nothing - } - - - @Nonnull - public Node getBottomClassNode() { - return new OWLClassNode(o.getOWLOntologyManager().getOWLDataFactory().getOWLNothing()); - } - - - @Nonnull - public Node getBottomDataPropertyNode() { - return new OWLDataPropertyNode(o.getOWLOntologyManager().getOWLDataFactory().getOWLBottomDataProperty()); - } - - - @Nonnull - public Node getBottomObjectPropertyNode() { - return new OWLObjectPropertyNode(o.getOWLOntologyManager().getOWLDataFactory().getOWLBottomObjectProperty()); - } - - - @Nonnull - public BufferingMode getBufferingMode() { - return BufferingMode.NON_BUFFERING; - } - - - @Nonnull - public NodeSet getDataPropertyDomains(@Nonnull OWLDataProperty pe, - boolean direct) { - final OWLClassNodeSet s = new OWLClassNodeSet(); - - for (final OWLDataPropertyDomainAxiom a : o.getAxioms(AxiomType.DATA_PROPERTY_DOMAIN)) { - if (a.getProperty().equals(pe) && !a.getDomain().isAnonymous()) { - s.addEntity(a.getDomain().asOWLClass()); - } - } - - return s; - } - - - @Nonnull - public Set getDataPropertyValues(@Nonnull OWLNamedIndividual ind, - @Nonnull OWLDataProperty pe) { - final Set literals = new HashSet<>(); - - for (final OWLDataPropertyAssertionAxiom a : o.getAxioms(AxiomType.DATA_PROPERTY_ASSERTION)) { - if (a.getProperty().equals(pe) && a.getSubject().equals(ind)) { - literals.add(a.getObject()); - } - } - - return literals; - } - - - @Nonnull - public NodeSet getDifferentIndividuals(@Nonnull OWLNamedIndividual ind) { - final OWLNamedIndividualNodeSet pn = new OWLNamedIndividualNodeSet(); - - for (final OWLDifferentIndividualsAxiom a : o.getAxioms(AxiomType.DIFFERENT_INDIVIDUALS)) { - if (a.getIndividuals().contains(ind)) { - for (OWLIndividual e : a.getIndividuals()) { - if (e.isNamed()) { - pn.addEntity(e.asOWLNamedIndividual()); - } - } - } - } - - return pn; - } - - @Nonnull - public Node getEquivalentClasses(@Nonnull OWLClassExpression ce) { - final OWLClassNode pn = new OWLClassNode(); - - for (final OWLEquivalentClassesAxiom a : o.getAxioms(AxiomType.EQUIVALENT_CLASSES)) { - if (a.getClassExpressions().contains(ce)) { - for (OWLClassExpression e : a.getClassExpressions()) { - if (!e.isAnonymous()) { - pn.add(e.asOWLClass()); - } - } - } - } - - return pn; - } - - - @Nonnull - public Node getEquivalentDataProperties(@Nonnull OWLDataProperty pe) { - final OWLDataPropertyNode pn = new OWLDataPropertyNode(pe); - - EntitySearcher.getEquivalentProperties(pe, o).filter(p -> !p.isAnonymous()) - .forEach(p -> pn.add(p.asOWLDataProperty())); - - pn.add(pe); - - return pn; - } - - - @Nonnull - public Node getEquivalentObjectProperties(@Nonnull OWLObjectPropertyExpression pe) { - final OWLObjectPropertyNode pn = new OWLObjectPropertyNode(); - - for (final OWLEquivalentObjectPropertiesAxiom a : o.getAxioms(AxiomType.EQUIVALENT_OBJECT_PROPERTIES)) { - if (a.getProperties().contains(pe)) { - for (OWLObjectPropertyExpression e : a.getProperties()) { - if (!e.isAnonymous()) { - pn.add(e.asOWLObjectProperty()); - } - } - } - } - - return pn; - } - - - @Nonnull - public IndividualNodeSetPolicy getIndividualNodeSetPolicy() { - return IndividualNodeSetPolicy.BY_NAME; - } - - - @Nonnull - public NodeSet getInstances(@Nonnull OWLClassExpression ce, boolean direct) { - final OWLNamedIndividualNodeSet s = new OWLNamedIndividualNodeSet(); - - for (final OWLClassAssertionAxiom a : o.getAxioms(AxiomType.CLASS_ASSERTION)) { - if (a.getClassExpression().equals(ce) && a.getIndividual().isNamed()) { - s.addEntity(a.getIndividual().asOWLNamedIndividual()); - } - } - return s; - } - - - @Nonnull - public Node getInverseObjectProperties(@Nonnull OWLObjectPropertyExpression pe) { - final OWLObjectPropertyNode s = new OWLObjectPropertyNode(); - - for (final OWLInverseObjectPropertiesAxiom a : o.getAxioms(AxiomType.INVERSE_OBJECT_PROPERTIES)) { - if (a.getFirstProperty().equals(pe) && !a.getSecondProperty().isAnonymous()) { - s.add(a.getSecondProperty().asOWLObjectProperty()); - } else if (a.getSecondProperty().equals(pe) && !a.getFirstProperty().isAnonymous()) { - s.add(a.getFirstProperty().asOWLObjectProperty()); - } - } - return s; - } - - - @Nonnull - public NodeSet getObjectPropertyDomains(@Nonnull OWLObjectPropertyExpression pe, boolean direct) { - final OWLClassNodeSet s = new OWLClassNodeSet(); - - for (final OWLObjectPropertyDomainAxiom a : o.getAxioms(AxiomType.OBJECT_PROPERTY_DOMAIN)) { - if (a.getProperty().equals(pe) && !a.getDomain().isAnonymous()) { - s.addEntity(a.getDomain().asOWLClass()); - } - } - return s; - } - - - @Nonnull - public NodeSet getObjectPropertyRanges( - @Nonnull OWLObjectPropertyExpression pe, boolean direct) { - final OWLClassNodeSet s = new OWLClassNodeSet(); - - for (final OWLObjectPropertyRangeAxiom a : o.getAxioms(AxiomType.OBJECT_PROPERTY_RANGE)) { - if (a.getProperty().equals(pe) && !a.getRange().isAnonymous()) { - s.addEntity(a.getRange().asOWLClass()); - } - } - return s; - } - - - @Nonnull - public NodeSet getObjectPropertyValues(@Nonnull OWLNamedIndividual ind, @Nonnull - OWLObjectPropertyExpression pe) { - final OWLNamedIndividualNodeSet pn = new OWLNamedIndividualNodeSet(); - - EntitySearcher.getObjectPropertyValues(ind, pe, o).filter(OWLIndividual::isNamed) - .forEach(i -> pn.addEntity(i.asOWLNamedIndividual())); - - return pn; - } - - - @Nonnull - public Set getPendingAxiomAdditions() { - return Collections.emptySet(); - } - - - @Nonnull - public Set getPendingAxiomRemovals() { - return Collections.emptySet(); - } - - - @Nonnull - public List getPendingChanges() { - return Collections.emptyList(); - } - - - @Nonnull - public String getReasonerName() { - return "identity reasoner"; - } - - - @Nonnull - public Version getReasonerVersion() { - return new Version(0, 1, 0, 1); - } - - - @Nonnull - public OWLOntology getRootOntology() { - return o; - } - - - @Nonnull - public Node getSameIndividuals(@Nonnull OWLNamedIndividual ind) { - final OWLNamedIndividualNode pn = new OWLNamedIndividualNode(); - - for (final OWLSameIndividualAxiom a : o.getAxioms(AxiomType.SAME_INDIVIDUAL)) { - if (a.getIndividuals().contains(ind)) { - for (OWLIndividual e : a.getIndividuals()) { - if (e.isNamed()) { - pn.add(e.asOWLNamedIndividual()); - } - } - } - } - return pn; - } - - - @Nonnull - public NodeSet getSubClasses(@Nonnull OWLClassExpression ce, boolean direct) { - final OWLClassNodeSet pn = new OWLClassNodeSet(); - - for (final OWLSubClassOfAxiom a : o.getAxioms(AxiomType.SUBCLASS_OF)) { - if (a.getSuperClass().equals(ce) && !a.getSubClass().isAnonymous()) { - pn.addEntity(a.getSubClass().asOWLClass()); - } - } - return pn; - } - - - @Nonnull - public NodeSet getSubDataProperties(@Nonnull OWLDataProperty pe, boolean direct) { - final OWLDataPropertyNodeSet pn = new OWLDataPropertyNodeSet(); - - for (final OWLSubDataPropertyOfAxiom a : o.getAxioms(AxiomType.SUB_DATA_PROPERTY)) { - if (a.getSuperProperty().equals(pe) && !a.getSubProperty().isAnonymous()) { - pn.addEntity(a.getSubProperty().asOWLDataProperty()); - } - } - return pn; - } - - - @Nonnull - public NodeSet getSubObjectProperties( - @Nonnull OWLObjectPropertyExpression pe, boolean direct) { - final OWLObjectPropertyNodeSet pn = new OWLObjectPropertyNodeSet(); - - for (final OWLSubObjectPropertyOfAxiom a : o.getAxioms(AxiomType.SUB_OBJECT_PROPERTY)) { - if (a.getSuperProperty().equals(pe) && !a.getSubProperty().isAnonymous()) { - pn.addEntity(a.getSubProperty().asOWLObjectProperty()); - } - } - return pn; - } - - - @Nonnull - public NodeSet getSuperClasses(@Nonnull OWLClassExpression ce, boolean direct) { - final OWLClassNodeSet pn = new OWLClassNodeSet(); - - for (final OWLSubClassOfAxiom a : o.getAxioms(AxiomType.SUBCLASS_OF)) { - if (a.getSubClass().equals(ce) && !a.getSuperClass().isAnonymous()) { - pn.addEntity(a.getSuperClass().asOWLClass()); - } - } - return pn; - } - - - @Nonnull - public NodeSet getSuperDataProperties(@Nonnull OWLDataProperty pe, boolean direct) { - final OWLDataPropertyNodeSet pn = new OWLDataPropertyNodeSet(); - - for (final OWLSubDataPropertyOfAxiom a : o.getAxioms(AxiomType.SUB_DATA_PROPERTY)) { - if (a.getSubProperty().equals(pe) && !a.getSuperProperty().isAnonymous()) { - pn.addEntity(a.getSuperProperty().asOWLDataProperty()); - } - } - return pn; - } - - - @Nonnull - public NodeSet getSuperObjectProperties( - @Nonnull OWLObjectPropertyExpression pe, boolean direct) { - final OWLObjectPropertyNodeSet pn = new OWLObjectPropertyNodeSet(); - - for (final OWLSubObjectPropertyOfAxiom a : o.getAxioms(AxiomType.SUB_OBJECT_PROPERTY)) { - if (a.getSubProperty().equals(pe) && !a.getSuperProperty().isAnonymous()) { - pn.addEntity(a.getSuperProperty().asOWLObjectProperty()); - } - } - return pn; - } - - - public long getTimeOut() { - return Long.MAX_VALUE; - } - - - @Nonnull - public Node getTopClassNode() { - return new OWLClassNode(o.getOWLOntologyManager().getOWLDataFactory().getOWLThing()); - } - - - @Nonnull - public Node getTopDataPropertyNode() { - return new OWLDataPropertyNode(o.getOWLOntologyManager().getOWLDataFactory().getOWLTopDataProperty()); - } - - - @Nonnull - public Node getTopObjectPropertyNode() { - return new OWLObjectPropertyNode(o.getOWLOntologyManager().getOWLDataFactory().getOWLTopObjectProperty()); - - } - - - @Nonnull - public NodeSet getTypes(@Nonnull OWLNamedIndividual ind, boolean direct) { - final OWLClassNodeSet s = new OWLClassNodeSet(); - EntitySearcher.getTypes(ind, o).filter(ce -> !ce.isAnonymous()).forEach(ce -> s.addEntity(ce.asOWLClass())); - - return s; - } - - - @Nonnull - public Node getUnsatisfiableClasses() { - return new OWLClassNode(); - } - - - public void interrupt() { - // do nothing - } - - - public boolean isConsistent() { - return true; - } - - - public boolean isEntailed(@Nonnull OWLAxiom axiom) { - return o.containsAxiom(axiom); - } - - - public boolean isEntailed(@Nonnull Set axioms) { - for (final OWLAxiom a : axioms) { - if (!o.containsAxiom(a)) { - return false; - } - } - return true; - } - - - public boolean isEntailmentCheckingSupported(@Nonnull AxiomType axiomType) { - return true; - } - - - public boolean isSatisfiable(@Nonnull OWLClassExpression classExpression) { - return true; - } - - - @Nonnull - public FreshEntityPolicy getFreshEntityPolicy() { - return FreshEntityPolicy.DISALLOW; - } - - - @Nonnull - public NodeSet getDisjointClasses(@Nonnull OWLClassExpression arg0) { - // TODO Auto-generated method stub - return null; - } - - - @Nonnull - public NodeSet getDisjointDataProperties( - @Nonnull OWLDataPropertyExpression arg0) { - // TODO Auto-generated method stub - return null; - } - - - @Nonnull - public NodeSet getDisjointObjectProperties( - @Nonnull OWLObjectPropertyExpression arg0) { - // TODO Auto-generated method stub - return null; - } - - - @Nonnull - public Set getPrecomputableInferenceTypes() { - // TODO Auto-generated method stub - return null; - } - - - public boolean isPrecomputed(@Nonnull InferenceType arg0) { - // TODO Auto-generated method stub - return false; - } - - - public void precomputeInferences(@Nonnull InferenceType... arg0) { - // TODO Auto-generated method stub - - } -} diff --git a/jopa-owlapi-utils/src/main/java/cz/cvut/kbss/jopa/owlapi/identityreasoner/OWLAPIIdentityReasonerFactory.java b/jopa-owlapi-utils/src/main/java/cz/cvut/kbss/jopa/owlapi/identityreasoner/OWLAPIIdentityReasonerFactory.java deleted file mode 100644 index f301e65cb..000000000 --- a/jopa-owlapi-utils/src/main/java/cz/cvut/kbss/jopa/owlapi/identityreasoner/OWLAPIIdentityReasonerFactory.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.owlapi.identityreasoner; - -import org.semanticweb.owlapi.model.OWLOntology; -import org.semanticweb.owlapi.reasoner.OWLReasoner; -import org.semanticweb.owlapi.reasoner.OWLReasonerConfiguration; -import org.semanticweb.owlapi.reasoner.OWLReasonerFactory; - -import javax.annotation.Nonnull; - -@Deprecated -public class OWLAPIIdentityReasonerFactory implements OWLReasonerFactory { - - @Nonnull - public OWLReasoner createNonBufferingReasoner(@Nonnull OWLOntology ontology) { - return createNonBufferingReasoner(ontology, null); - } - - @Nonnull - public OWLReasoner createNonBufferingReasoner(@Nonnull OWLOntology ontology, - @Nonnull OWLReasonerConfiguration config) { - return new OWLAPIIdentityReasoner(ontology); - } - - @Nonnull - public OWLReasoner createReasoner(@Nonnull OWLOntology ontology) { - return createReasoner(ontology, null); - } - - @Nonnull - public OWLReasoner createReasoner(@Nonnull OWLOntology ontology, - @Nonnull OWLReasonerConfiguration config) { - return new OWLAPIIdentityReasoner(ontology); - } - - @Nonnull - public String getReasonerName() { - return "Identity reasoner"; - } -} diff --git a/jopa-owlapi-utils/src/main/java/cz/cvut/kbss/jopa/util/MappingFileParser.java b/jopa-owlapi-utils/src/main/java/cz/cvut/kbss/jopa/util/MappingFileParser.java index e739f814c..3070c0341 100644 --- a/jopa-owlapi-utils/src/main/java/cz/cvut/kbss/jopa/util/MappingFileParser.java +++ b/jopa-owlapi-utils/src/main/java/cz/cvut/kbss/jopa/util/MappingFileParser.java @@ -40,7 +40,7 @@ public final class MappingFileParser { /** * Default delimiter of mappings in the mapping file. *

    - * That is, on the left hand side of the delimiter would the original IRI and on the right hand side is the mapped + * That is, on the left-hand side of the delimiter would the original IRI and on the right-hand side is the mapped * value. */ public static final String DEFAULT_DELIMITER = ">"; diff --git a/modelgen/pom.xml b/modelgen/pom.xml index c27bc5e05..659187942 100644 --- a/modelgen/pom.xml +++ b/modelgen/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 1.2.2 + 2.0.0 ../pom.xml @@ -63,4 +63,4 @@ - \ No newline at end of file + diff --git a/ontodriver-api/pom.xml b/ontodriver-api/pom.xml index 2a842381d..e76d87cad 100644 --- a/ontodriver-api/pom.xml +++ b/ontodriver-api/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 1.2.2 + 2.0.0 ../pom.xml diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/DriverConfigParam.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/DriverConfigParam.java index 0deef5dac..9e8329c0a 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/DriverConfigParam.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/DriverConfigParam.java @@ -26,12 +26,6 @@ public enum DriverConfigParam implements ConfigurationParameter { AUTO_COMMIT(OntoDriverProperties.CONNECTION_AUTO_COMMIT), REASONER_FACTORY_CLASS(OntoDriverProperties.REASONER_FACTORY_CLASS), - /** - * @deprecated Ontology language should be set at persistence unit level. The driver uses language specified per - * {@link cz.cvut.kbss.ontodriver.model.Assertion}. - */ - @Deprecated - ONTOLOGY_LANGUAGE(OntoDriverProperties.ONTOLOGY_LANGUAGE), USE_TRANSACTIONAL_ONTOLOGY(OntoDriverProperties.USE_TRANSACTIONAL_ONTOLOGY), MODULE_EXTRACTION_SIGNATURE(OntoDriverProperties.MODULE_EXTRACTION_SIGNATURE); diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/OntoDriverProperties.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/OntoDriverProperties.java index 5fd717146..ba7447ca5 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/OntoDriverProperties.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/OntoDriverProperties.java @@ -42,15 +42,6 @@ public final class OntoDriverProperties { */ public static final String REASONER_FACTORY_CLASS = "cz.cvut.jopa.reasonerFactoryClass"; - /** - * Ontology language property. - * - * @deprecated Ontology language should be set at persistence unit level. The driver uses language specified per - * {@link cz.cvut.kbss.ontodriver.model.Assertion}. - */ - @Deprecated - public static final String ONTOLOGY_LANGUAGE = "cz.cvut.jopa.lang"; - /** * This setting tells the driver whether to use the transactional ontology for retrieving entities and answering * queries. diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ListDescriptor.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ListDescriptor.java index 0ebfa5f0d..5614f3281 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ListDescriptor.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ListDescriptor.java @@ -23,7 +23,7 @@ import java.net.URI; /** - * This interface declares the basic methods for working with sequences in JOPA. + * Describes storage of a list. */ public interface ListDescriptor { @@ -44,8 +44,7 @@ public interface ListDescriptor { /** * Gets owner of the list. *

    - * That is, the named resource which is at the head of the list. In object - * model, it is the owning entity. + * That is, the named resource which is at the head of the list. In object model, it is the owning entity. * * @return List owner */ @@ -65,4 +64,4 @@ public interface ListDescriptor { * @return Property assertion */ Assertion getNextNode(); -} \ No newline at end of file +} diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListDescriptor.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListDescriptor.java index 36834c7dc..3c7e9863a 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListDescriptor.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListDescriptor.java @@ -19,6 +19,11 @@ import cz.cvut.kbss.ontodriver.model.Assertion; +/** + * Describes storage of a referenced list. + *

    + * A referenced list consists of nodes representing the list structure and values to which each node points. + */ public interface ReferencedListDescriptor extends ListDescriptor { /** @@ -27,4 +32,11 @@ public interface ReferencedListDescriptor extends ListDescriptor { * @return Property assertion */ Assertion getNodeContent(); + + /** + * Whether the list is terminated by {@literal rdf:nil}. + * + * @return {@code true} if the list is terminated by nil, {@code false} otherwise + */ + boolean isTerminatedByNil(); } diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListDescriptorImpl.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListDescriptorImpl.java index 8c38ecf8a..0f1f9b44c 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListDescriptorImpl.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListDescriptorImpl.java @@ -27,17 +27,27 @@ * Represents singly-linked referenced list. *

    * In referenced list each node has content linked to it by a separate property. In addition, each node points to its - * successor in the sequence (with the exception of the last node, which has no successor). + * successor in the sequence. The last node may point to {@literal rdf:nil} or it may just lack a successor. */ public class ReferencedListDescriptorImpl implements ReferencedListDescriptor { protected final ListDescriptor descriptor; private final Assertion nodeContent; + private final boolean terminatedByNil; + public ReferencedListDescriptorImpl(NamedResource listOwner, Assertion listProperty, Assertion nextNode, Assertion nodeContent) { this.descriptor = new BaseListDescriptorImpl(listOwner, listProperty, nextNode); this.nodeContent = Objects.requireNonNull(nodeContent); + this.terminatedByNil = false; + } + + public ReferencedListDescriptorImpl(NamedResource listOwner, Assertion listProperty, + Assertion nextNode, Assertion nodeContent, boolean terminatedByNil) { + this.descriptor = new BaseListDescriptorImpl(listOwner, listProperty, nextNode); + this.nodeContent = Objects.requireNonNull(nodeContent); + this.terminatedByNil = terminatedByNil; } @Override @@ -70,25 +80,28 @@ public Assertion getNodeContent() { return nodeContent; } + @Override + public boolean isTerminatedByNil() { + return terminatedByNil; + } + @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + descriptor.hashCode(); result = prime * result + nodeContent.hashCode(); + result = prime * result + Boolean.hashCode(terminatedByNil); return result; } @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) {return true;} + if (obj == null) {return false;} + if (getClass() != obj.getClass()) {return false;} ReferencedListDescriptorImpl other = (ReferencedListDescriptorImpl) obj; - return descriptor.equals(other.descriptor) && nodeContent.equals(other.nodeContent); + return descriptor.equals(other.descriptor) && nodeContent.equals(other.nodeContent) && terminatedByNil == other.terminatedByNil; } @Override diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListValueDescriptor.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListValueDescriptor.java index df420d7c4..002eef180 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListValueDescriptor.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/descriptor/ReferencedListValueDescriptor.java @@ -25,14 +25,23 @@ import java.util.List; import java.util.Objects; +/** + * Descriptor of values of a referenced list. + * + * @param List value type + */ public class ReferencedListValueDescriptor extends ReferencedListDescriptorImpl implements ListValueDescriptor { - private final List values; + private final List values = new ArrayList<>(); public ReferencedListValueDescriptor(NamedResource listOwner, Assertion listProperty, Assertion nextNode, Assertion nodeContent) { super(listOwner, listProperty, nextNode, nodeContent); - this.values = new ArrayList<>(); + } + + public ReferencedListValueDescriptor(NamedResource listOwner, Assertion listProperty, + Assertion nextNode, Assertion nodeContent, boolean terminatedByNil) { + super(listOwner, listProperty, nextNode, nodeContent, terminatedByNil); } @Override @@ -56,15 +65,11 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null || getClass() != obj.getClass()) - return false; + if (this == obj) {return true;} + if (obj == null || getClass() != obj.getClass()) {return false;} ReferencedListValueDescriptor other = (ReferencedListValueDescriptor) obj; - if (!descriptor.equals(other.descriptor)) - return false; - if (!getNodeContent().equals(other.getNodeContent())) - return false; + if (!descriptor.equals(other.descriptor)) {return false;} + if (!getNodeContent().equals(other.getNodeContent())) {return false;} return values.equals(other.values); } diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/util/ErrorUtils.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/util/ErrorUtils.java index efddc895e..ffbba3935 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/util/ErrorUtils.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/util/ErrorUtils.java @@ -19,6 +19,7 @@ import java.util.function.Supplier; +// TODO Remove public class ErrorUtils { /** diff --git a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/ResultSetTest.java b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/ResultSetTest.java index 364d45673..031c44a79 100644 --- a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/ResultSetTest.java +++ b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/ResultSetTest.java @@ -82,4 +82,4 @@ void streamReturnsStreamOverResultSetInstance() { final Stream result = sut.stream(); assertNotNull(result); } -} \ No newline at end of file +} diff --git a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/iteration/DelegatingResultRowTest.java b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/iteration/DelegatingResultRowTest.java index 7d18ef4d4..15a5cfaf6 100644 --- a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/iteration/DelegatingResultRowTest.java +++ b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/iteration/DelegatingResultRowTest.java @@ -190,4 +190,4 @@ void getIndexDelegatesCallToResultSet() throws OntoDriverException { sut.getIndex(); verify(resultSet).getRowIndex(); } -} \ No newline at end of file +} diff --git a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/model/AssertionTest.java b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/model/AssertionTest.java index a02a70411..c53d4b197 100644 --- a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/model/AssertionTest.java +++ b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/model/AssertionTest.java @@ -46,4 +46,4 @@ void equalsReturnsFalseWhenAssertionsDifferInInferenceType() { final Assertion aTwo = Assertion.createDataPropertyAssertion(ID, true); assertNotEquals(aOne, aTwo); } -} \ No newline at end of file +} diff --git a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/util/StatementHolderTest.java b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/util/StatementHolderTest.java index 9880104e2..757135d78 100644 --- a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/util/StatementHolderTest.java +++ b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/util/StatementHolderTest.java @@ -141,4 +141,4 @@ void setUnknownParameterValueThrowsException() { holder.analyzeStatement(); assertThrows(IllegalArgumentException.class, () -> holder.setParameter("z", "someValue@en")); } -} \ No newline at end of file +} diff --git a/ontodriver-jena/pom.xml b/ontodriver-jena/pom.xml index 38d0b7330..c07fc74bb 100644 --- a/ontodriver-jena/pom.xml +++ b/ontodriver-jena/pom.xml @@ -8,7 +8,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml @@ -17,7 +17,7 @@ Jena implementation of the OntoDriver API - 4.10.0 + 5.0.0 diff --git a/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListHandler.java b/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListHandler.java index 939a4cd5b..f65e6a7ff 100644 --- a/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListHandler.java +++ b/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListHandler.java @@ -24,13 +24,14 @@ import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.Statement; +import org.apache.jena.vocabulary.RDF; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; +import java.util.Optional; import static org.apache.jena.rdf.model.ResourceFactory.createProperty; import static org.apache.jena.rdf.model.ResourceFactory.createResource; @@ -74,7 +75,14 @@ void appendNewNodes(ReferencedListValueDescriptor descriptor, int i, Reso appendNode(lastNode, descriptor.getValues().get(i), i == 0 ? hasList : hasNext, hasContent, context, toAdd, descriptor, i); } - connector.add(toAdd, context); + if (i > 0) { + createNilTerminal(lastNode, hasNext, descriptor).ifPresent(toAdd::add); + connector.add(toAdd, context); + } + } + + private void removePreviousNilTerminal(Resource lastNode, Property hasNext, String context) { + connector.remove(lastNode, hasNext, RDF.nil, context); } private Resource appendNode(Resource previousNode, V value, Property link, Property hasContent, @@ -84,7 +92,7 @@ private Resource appendNode(Resource previousNode, V value, Property link, P statements.add(createStatement(previousNode, link, node)); statements.addAll(ReferencedListHelper.toRdfNodes(value, descriptor.getNodeContent()) .map(n -> createStatement(node, hasContent, n)) - .collect(Collectors.toList())); + .toList()); return node; } @@ -99,27 +107,38 @@ private Resource generateNewListNode(URI baseUri, String context, int index) { return node; } + private Optional createNilTerminal(Resource lastNode, Property hasNext, + ReferencedListDescriptor descriptor) { + return descriptor.isTerminatedByNil() ? Optional.of(createStatement(lastNode, hasNext, RDF.nil)) : Optional.empty(); + } + void updateList(ReferencedListValueDescriptor descriptor) { final ReferencedListIterator it = new ReferencedListIterator<>(descriptor, connector); int i = 0; + Resource lastNode = it.getCurrentNode(); while (it.hasNext() && i < descriptor.getValues().size()) { final V update = descriptor.getValues().get(i); final V existing = it.nextValue(); if (!existing.equals(update)) { it.replace(update); } + lastNode = it.getCurrentNode(); i++; } - removeObsoleteNodes(it); - if (i < descriptor.getValues().size()) { - appendNewNodes(descriptor, i, it.getCurrentNode()); - } + removeObsoleteNodes(it, descriptor.isTerminatedByNil()); + assert lastNode != null; + appendNewNodes(descriptor, i, lastNode); } - private static void removeObsoleteNodes(ReferencedListIterator it) { + private void removeObsoleteNodes(ReferencedListIterator it, boolean removeNilTerminal) { + Resource lastNode = it.getCurrentNode(); while (it.hasNext()) { it.nextValue(); + lastNode = it.getCurrentNode(); it.removeWithoutReconnect(); } + if (removeNilTerminal) { + removePreviousNilTerminal(lastNode, it.hasNextProperty, it.context); + } } } diff --git a/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListIterator.java b/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListIterator.java index b4b8d2dc3..ab482f201 100644 --- a/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListIterator.java +++ b/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListIterator.java @@ -31,6 +31,7 @@ import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.Statement; +import org.apache.jena.vocabulary.RDF; import java.util.Collection; import java.util.HashSet; @@ -52,6 +53,16 @@ class ReferencedListIterator extends AbstractListIterator { this.hasContent = createProperty(hasContentAssertion.getIdentifier().toString()); } + @Override + boolean hasNext() { + return super.hasNext() && !isNextNil(); + } + + private boolean isNextNil() { + assert cursor != null; + return !cursor.isEmpty() && RDF.nil.equals(cursor.iterator().next().getObject()); + } + @Override Axiom nextAxiom() { final T value = nextValue(); diff --git a/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListHandlerTest.java b/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListHandlerTest.java index 8854cb3d9..e3b99b339 100644 --- a/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListHandlerTest.java +++ b/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/list/ReferencedListHandlerTest.java @@ -31,6 +31,7 @@ import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.Statement; +import org.apache.jena.vocabulary.RDF; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -41,25 +42,35 @@ import java.net.URI; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.apache.jena.rdf.model.ResourceFactory.createProperty; import static org.apache.jena.rdf.model.ResourceFactory.createResource; import static org.apache.jena.rdf.model.ResourceFactory.createStatement; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -353,4 +364,114 @@ void persistListSavesMultilingualStringTranslationsAsContentOfSingleNode() { i++; } } + + @Test + void persistListAppendsRdfNilAfterLastNodeWhenDescriptorIsConfiguredToTerminateListWithIt() { + final List values = listUtil.generateList(); + final ReferencedListValueDescriptor desc = new ReferencedListValueDescriptor<>(OWNER, HAS_LIST, HAS_NEXT, HAS_CONTENT, true); + values.forEach(v -> desc.addValue(NamedResource.create(v))); + + sut.persistList(desc); + final ArgumentCaptor captor = ArgumentCaptor.forClass(List.class); + verify(connectorMock).add(captor.capture(), isNull()); + final Collection stmts = captor.getValue(); + final Iterator it = stmts.iterator(); + Statement statement = null; + while (it.hasNext()) { + statement = it.next(); + } + assertNotNull(statement); + assertEquals(HAS_NEXT_PROPERTY, statement.getPredicate()); + assertEquals(RDF.nil, statement.getObject()); + } + + @Test + void loadListLoadsNilTerminatedList() { + final ReferencedListDescriptor descriptor = new ReferencedListDescriptorImpl(OWNER, HAS_LIST, HAS_NEXT, HAS_CONTENT, true); + final List refList = generateList(null); + final List nodes = listUtil.getReferencedListNodes(); + final Resource lastNodeIri = nodes.get(nodes.size() - 1); + when(connectorMock.find(lastNodeIri, HAS_NEXT_PROPERTY, null, Collections.emptySet())).thenReturn( + List.of(createStatement(lastNodeIri, HAS_NEXT_PROPERTY, RDF.nil))); + final Collection> res = sut.loadList(descriptor); + assertEquals(refList.size(), res.size()); + for (Axiom a : res) { + assertInstanceOf(NamedResource.class, a.getValue().getValue()); + assertTrue(refList.contains(((NamedResource) a.getValue().getValue()).getIdentifier())); + } + } + + @Test + void clearListRemovesListNodesIncludingNilTerminal() { + final ReferencedListValueDescriptor desc = new ReferencedListValueDescriptor<>(OWNER, HAS_LIST, HAS_NEXT, HAS_CONTENT, true); + // Generate old list + generateList(null); + final List nodes = listUtil.getReferencedListNodes(); + final Resource lastNodeIri = nodes.get(nodes.size() - 1); + when(connectorMock.find(lastNodeIri, HAS_NEXT_PROPERTY, null, Collections.emptySet())).thenReturn( + List.of(createStatement(lastNodeIri, HAS_NEXT_PROPERTY, RDF.nil))); + + sut.updateList(desc); + final ArgumentCaptor removedCaptor = ArgumentCaptor.forClass(Resource.class); + verify(connectorMock, times(nodes.size() * 2 + 1)).remove(any(Resource.class), any(Property.class), removedCaptor.capture(), any()); + verify(connectorMock, never()).add(anyList(), any()); + assertEquals(nodes.size() * 2 + 1, removedCaptor.getAllValues().size()); + nodes.forEach(node -> assertThat(removedCaptor.getAllValues(), hasItem(node))); + assertThat(removedCaptor.getAllValues(), hasItem(RDF.nil)); + } + + @Test + void updateListRemovesNilTerminalFromPreviouslyLastNodeAndAppendsItToNewAddedLastNode() { + final ReferencedListValueDescriptor desc = new ReferencedListValueDescriptor<>(OWNER, HAS_LIST, HAS_NEXT, HAS_CONTENT, true); + final List addedItems = IntStream.range(0, 5) + .mapToObj(i -> NamedResource.create(Generator.generateUri())) + .toList(); + final List oldList = generateList(null); + final List oldNodes = listUtil.getReferencedListNodes(); + final Resource lastNodeIri = oldNodes.get(oldNodes.size() - 1); + when(connectorMock.find(lastNodeIri, HAS_NEXT_PROPERTY, null, Collections.emptySet())) + .thenReturn(Set.of(createStatement(lastNodeIri, HAS_NEXT_PROPERTY, RDF.nil))); + // The original items + for (URI item : oldList) { + desc.addValue(NamedResource.create(item)); + } + // Now add the new ones + for (NamedResource r : addedItems) { + desc.addValue(r); + } + + sut.updateList(desc); + verify(connectorMock).remove(lastNodeIri, HAS_NEXT_PROPERTY, RDF.nil, null); + final ArgumentCaptor> addedCaptor = ArgumentCaptor.forClass(List.class); + verify(connectorMock, atLeast(1)).add(addedCaptor.capture(), isNull()); + // Added nodes with values + terminal + assertEquals(addedItems.size() * 2 + 1, addedCaptor.getValue().size()); + for (Statement stmt : addedCaptor.getValue()) { + if (stmt.getPredicate().equals(HAS_CONTENT_PROPERTY)) { + final URI u = URI.create(stmt.getObject().asResource().getURI()); + assertTrue(addedItems.contains(NamedResource.create(u))); + } + } + assertTrue(addedCaptor.getValue().stream().anyMatch(s -> s.getObject().equals(RDF.nil))); + } + + @Test + void updateListRemovesNilTerminalFromLastNodeWhenItIsRemovedAndAddsItToNewLastNode() { + final ReferencedListValueDescriptor desc = new ReferencedListValueDescriptor<>(OWNER, HAS_LIST, HAS_NEXT, HAS_CONTENT, true); + final List oldList = generateList(null); + final List oldNodes = listUtil.getReferencedListNodes(); + final Resource lastNode = oldNodes.get(oldNodes.size() - 1); + when(connectorMock.find(lastNode, HAS_NEXT_PROPERTY, null, Collections.emptySet())) + .thenReturn(Set.of(createStatement(lastNode, HAS_NEXT_PROPERTY, RDF.nil))); + // The original items + for (URI item : oldList.subList(0, oldList.size() / 2)) { + desc.addValue(NamedResource.create(item)); + } + sut.updateList(desc); + verify(connectorMock).remove(lastNode, HAS_NEXT_PROPERTY, RDF.nil, null); + final ArgumentCaptor> addedCaptor = ArgumentCaptor.forClass(List.class); + verify(connectorMock).add(addedCaptor.capture(), isNull()); + assertTrue(addedCaptor.getValue().stream().anyMatch(s -> s.getObject().equals(RDF.nil))); + assertThat(addedCaptor.getValue(), not(hasItem(createStatement(lastNode, HAS_NEXT_PROPERTY, RDF.nil)))); + } } diff --git a/ontodriver-owlapi/pom.xml b/ontodriver-owlapi/pom.xml index 0c0644a1f..9e71fcb2b 100644 --- a/ontodriver-owlapi/pom.xml +++ b/ontodriver-owlapi/pom.xml @@ -11,7 +11,7 @@ cz.cvut.kbss.jopa jopa-all - 1.2.2 + 2.0.0 ../pom.xml diff --git a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/connector/BasicStorageConnector.java b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/connector/BasicStorageConnector.java index a4adbe78a..3dcdd45b3 100644 --- a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/connector/BasicStorageConnector.java +++ b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/connector/BasicStorageConnector.java @@ -33,7 +33,6 @@ import org.semanticweb.owlapi.apibinding.OWLManager; import org.semanticweb.owlapi.model.AddImport; import org.semanticweb.owlapi.model.IRI; -import org.semanticweb.owlapi.model.OWLDataFactory; import org.semanticweb.owlapi.model.OWLOntology; import org.semanticweb.owlapi.model.OWLOntologyChange; import org.semanticweb.owlapi.model.OWLOntologyCreationException; @@ -236,7 +235,6 @@ public void applyChanges(List changes) { assert changes != null; WRITE.lock(); try { - final OWLDataFactory df = ontologyManager.getOWLDataFactory(); final List toApply = changes.stream() .flatMap(o -> o.toOwlChanges(ontology).stream()) .collect(Collectors.toList()); diff --git a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/list/ReferencedListHandler.java b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/list/ReferencedListHandler.java index 6f80188fb..abd738afa 100644 --- a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/list/ReferencedListHandler.java +++ b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/list/ReferencedListHandler.java @@ -62,9 +62,23 @@ public void persistList(ReferencedListValueDescriptor descriptor) { if (descriptor.getValues().isEmpty()) { return; } + verifyNotRdfList(descriptor); owlapiAdapter.addTransactionalChanges(snapshot.applyChanges(createListAxioms(descriptor))); } + /** + * OWL does no support RDF lists because it uses them internally, and it would not be possible to distinguish + * internal RDF lists from user-defined ones. + * + * @param descriptor Value descriptor + * @throws IllegalArgumentException When the descriptor describes an RDF list terminated by RDF nil + */ + private static void verifyNotRdfList(ReferencedListValueDescriptor descriptor) { + if (descriptor.isTerminatedByNil()) { + throw new IllegalArgumentException("RDF nil-terminated lists are not supported by the OWL API driver."); + } + } + ReferencedListIterator iterator(ListDescriptor descriptor) { assert descriptor instanceof ReferencedListDescriptor; @@ -93,6 +107,7 @@ public void updateList(ReferencedListValueDescriptor descriptor) { } else if (isOrigEmpty(descriptor)) { persistList(descriptor); } else { + verifyNotRdfList(descriptor); mergeLists(descriptor); } } diff --git a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/query/NoOpReasoner.java b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/query/NoOpReasoner.java index df4f9c90c..187453928 100644 --- a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/query/NoOpReasoner.java +++ b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/query/NoOpReasoner.java @@ -38,7 +38,7 @@ * true} for direct whenever the called method allows it. All other calls are just forwarded to the underlying * reasoner. */ -class NoOpReasoner implements OWLReasoner { +public class NoOpReasoner implements OWLReasoner { private final OWLReasoner wrapped; diff --git a/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/list/ReferencedListHandlerTest.java b/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/list/ReferencedListHandlerTest.java index 7d31e2c0d..29cf0ca77 100644 --- a/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/list/ReferencedListHandlerTest.java +++ b/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/list/ReferencedListHandlerTest.java @@ -231,7 +231,7 @@ private void verifyAddedChanges(List changes, List expectedElements) { for (Object change : changes) { final AddAxiom ax = (AddAxiom) change; final OWLAxiom axiom = ax.getAxiom(); - assertTrue(axiom instanceof OWLObjectPropertyAssertionAxiom); + assertInstanceOf(OWLObjectPropertyAssertionAxiom.class, axiom); final OWLObjectPropertyAssertionAxiom assertion = (OWLObjectPropertyAssertionAxiom) axiom; OWLObjectProperty property = axiom.objectPropertiesInSignature().iterator().next(); if (property.equals(hasContentProperty)) { @@ -409,4 +409,22 @@ void persistListSavesMultilingualStringTranslationsAsContentOfSingleNode() { } } } + + @Test + void persistListThrowsIllegalArgumentExceptionForDescriptorRequestingNilTermination() { + this.valueDescriptor = new ReferencedListValueDescriptor<>(ListTestHelper.SUBJECT, ListTestHelper.HAS_LIST, ListTestHelper.HAS_NEXT, ListTestHelper.HAS_CONTENT, true); + ListTestHelper.LIST_ITEMS.forEach(item -> valueDescriptor.addValue(NamedResource.create(item))); + + assertThrows(IllegalArgumentException.class, () -> sut.persistList(valueDescriptor)); + } + + @Test + void updateListThrowsIllegalArgumentExceptionForDescriptorRequestingNilTermination() { + testHelper.persistList(ListTestHelper.LIST_ITEMS); + this.valueDescriptor = new ReferencedListValueDescriptor<>(ListTestHelper.SUBJECT, ListTestHelper.HAS_LIST, ListTestHelper.HAS_NEXT, ListTestHelper.HAS_CONTENT, true); + final List subList = ListTestHelper.LIST_ITEMS.subList(0, 5); + subList.forEach(item -> valueDescriptor.addValue(NamedResource.create(item))); + + assertThrows(IllegalArgumentException.class, () -> sut.updateList(valueDescriptor)); + } } diff --git a/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/query/AbstractResultSetTest.java b/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/query/AbstractResultSetTest.java index 73c9ec076..738241fdd 100644 --- a/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/query/AbstractResultSetTest.java +++ b/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/query/AbstractResultSetTest.java @@ -59,4 +59,4 @@ private QueryResult initSelectResult() { when(qr.getResultVars()).thenReturn(Collections.singletonList(v)); return qr; } -} \ No newline at end of file +} diff --git a/ontodriver-rdf4j/pom.xml b/ontodriver-rdf4j/pom.xml index 0383a6ff2..2953faf81 100644 --- a/ontodriver-rdf4j/pom.xml +++ b/ontodriver-rdf4j/pom.xml @@ -10,11 +10,11 @@ jopa-all cz.cvut.kbss.jopa - 1.2.2 + 2.0.0 - 4.3.8 + 4.3.11 2.0.9 diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoader.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoader.java index 5b0b9e289..3cbdb0e12 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoader.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoader.java @@ -22,7 +22,7 @@ import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; import cz.cvut.kbss.ontodriver.rdf4j.config.RuntimeConfiguration; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.loader.StatementLoader; import cz.cvut.kbss.ontodriver.rdf4j.util.AxiomBuilder; @@ -33,7 +33,7 @@ class AxiomLoader { - private final Connector connector; + private final RepoConnection connector; private final Map propertyToAssertion; private Map explicitAssertions; @@ -41,7 +41,7 @@ class AxiomLoader { private final RuntimeConfiguration config; - AxiomLoader(Connector connector, RuntimeConfiguration config) { + AxiomLoader(RepoConnection connector, RuntimeConfiguration config) { this.connector = connector; this.propertyToAssertion = new HashMap<>(); this.config = config; diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomSaver.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomSaver.java index dffb2e2bf..2ec673c48 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomSaver.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomSaver.java @@ -21,7 +21,7 @@ import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.NamedResource; import cz.cvut.kbss.ontodriver.model.Value; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.util.Rdf4jUtils; import cz.cvut.kbss.ontodriver.rdf4j.util.ValueConverter; @@ -34,10 +34,10 @@ class AxiomSaver { - private final Connector connector; + private final RepoConnection connector; private final ValueConverter valueConverter; - AxiomSaver(Connector connector) { + AxiomSaver(RepoConnection connector) { this.connector = connector; this.valueConverter = new ValueConverter(connector.getValueFactory()); } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/EpistemicAxiomRemover.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/EpistemicAxiomRemover.java index dd78c416c..26ba65f24 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/EpistemicAxiomRemover.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/EpistemicAxiomRemover.java @@ -21,7 +21,7 @@ import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.NamedResource; import cz.cvut.kbss.ontodriver.model.Value; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.connector.SubjectPredicateContext; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.util.ValueConverter; @@ -49,10 +49,10 @@ */ class EpistemicAxiomRemover { - private final Connector connector; + private final RepoConnection connector; private final ValueFactory valueFactory; - EpistemicAxiomRemover(Connector connector, ValueFactory valueFactory) { + EpistemicAxiomRemover(RepoConnection connector, ValueFactory valueFactory) { this.connector = connector; this.valueFactory = valueFactory; } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapter.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapter.java index 0805b3336..3f6e5ec66 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapter.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapter.java @@ -26,7 +26,7 @@ import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.rdf4j.config.Constants; import cz.cvut.kbss.ontodriver.rdf4j.config.RuntimeConfiguration; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.connector.StatementExecutor; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.list.ReferencedListHandler; @@ -55,14 +55,14 @@ class Rdf4jAdapter implements Closeable, Wrapper { */ private static final int ID_GENERATION_THRESHOLD = 64; - private final Connector connector; + private final RepoConnection connector; private final ValueFactory valueFactory; private final RuntimeConfiguration config; private final Transaction transaction; private boolean open = true; - public Rdf4jAdapter(Connector connector, RuntimeConfiguration config) { + public Rdf4jAdapter(RepoConnection connector, RuntimeConfiguration config) { assert connector != null; this.connector = connector; @@ -71,7 +71,7 @@ public Rdf4jAdapter(Connector connector, RuntimeConfiguration config) { this.transaction = new Transaction(); } - Connector getConnector() { + RepoConnection getConnector() { return connector; } @@ -147,6 +147,7 @@ URI generateIdentifier(URI classUri) throws Rdf4jDriverException { } private void startTransactionIfNotActive() throws Rdf4jDriverException { + // TODO Consider whether a transaction should be started for read operations if (!transaction.isActive()) { connector.begin(); transaction.begin(); diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jDriver.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jDriver.java index 8e608f558..1f357514c 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jDriver.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jDriver.java @@ -26,7 +26,7 @@ import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; import cz.cvut.kbss.ontodriver.rdf4j.config.RuntimeConfiguration; -import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectorFactory; +import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectionFactory; import cz.cvut.kbss.ontodriver.rdf4j.connector.init.FactoryOfFactories; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.loader.StatementLoaderFactory; @@ -45,7 +45,7 @@ class Rdf4jDriver implements Closeable, ConnectionListener { private final DriverConfiguration configuration; private boolean open; - private final ConnectorFactory connectorFactory; + private final ConnectionFactory connectionFactory; private final StatementLoaderFactory statementLoaderFactory; private final Set openedConnections; @@ -59,7 +59,7 @@ class Rdf4jDriver implements Closeable, ConnectionListener { configuration.addConfiguration(properties, CONFIGS); this.openedConnections = new HashSet<>(); final FactoryOfFactories factory = new FactoryOfFactories(configuration); - this.connectorFactory = factory.createConnectorFactory(); + this.connectionFactory = factory.createConnectorFactory(); this.statementLoaderFactory = factory.createStatementLoaderFactory(); this.open = true; } @@ -74,7 +74,7 @@ public void close() throws OntoDriverException { c.removeListener(); c.close(); } - connectorFactory.close(); + connectionFactory.close(); } catch (OntoDriverException e) { throw e; } catch (Exception e) { @@ -93,7 +93,7 @@ Connection acquireConnection() { assert open; final RuntimeConfiguration config = new RuntimeConfiguration(configuration); config.setStatementLoaderFactory(statementLoaderFactory); - final Rdf4jAdapter adapter = new Rdf4jAdapter(connectorFactory.createStorageConnector(), config); + final Rdf4jAdapter adapter = new Rdf4jAdapter(connectionFactory.createStorageConnection(), config); final Rdf4jConnection c = new Rdf4jConnection(adapter); c.setLists(new Rdf4jLists(adapter, c::ensureOpen, c::commitIfAuto)); c.setTypes(new Rdf4jTypes(adapter, c::ensureOpen, c::commitIfAuto)); @@ -120,6 +120,6 @@ public void connectionClosed(Rdf4jConnection connection) { */ void setRepository(Repository repository) throws Rdf4jDriverException { assert open; - connectorFactory.setRepository(repository); + connectionFactory.setRepository(repository); } } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jProperties.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jProperties.java index ff92aefe3..1a020ff95 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jProperties.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jProperties.java @@ -24,7 +24,7 @@ import cz.cvut.kbss.ontodriver.model.NamedResource; import cz.cvut.kbss.ontodriver.model.Value; import cz.cvut.kbss.ontodriver.rdf4j.config.RuntimeConfiguration; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import java.net.URI; @@ -34,7 +34,7 @@ class Rdf4jProperties implements Properties { - private final Connector connector; + private final RepoConnection connector; private final RuntimeConfiguration config; private final Procedure beforeCallback; diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/TypesHandler.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/TypesHandler.java index 5501beed7..4419673ba 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/TypesHandler.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/TypesHandler.java @@ -18,7 +18,7 @@ package cz.cvut.kbss.ontodriver.rdf4j; import cz.cvut.kbss.ontodriver.model.*; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.util.Rdf4jUtils; import org.eclipse.rdf4j.model.Resource; @@ -32,10 +32,10 @@ class TypesHandler { - private final Connector connector; + private final RepoConnection connector; private final ValueFactory valueFactory; - TypesHandler(Connector connector, ValueFactory valueFactory) { + TypesHandler(RepoConnection connector, ValueFactory valueFactory) { this.connector = connector; this.valueFactory = valueFactory; } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jConfigParam.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jConfigParam.java index f9b25b98f..d26ac8651 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jConfigParam.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jConfigParam.java @@ -37,7 +37,8 @@ public enum Rdf4jConfigParam implements ConfigurationParameter { RECONNECT_ATTEMPTS(Rdf4jOntoDriverProperties.RECONNECT_ATTEMPTS), INFERENCE_IN_DEFAULT_CONTEXT(Rdf4jOntoDriverProperties.INFERENCE_IN_DEFAULT_CONTEXT), CONNECTION_REQUEST_TIMEOUT(Rdf4jOntoDriverProperties.CONNECTION_REQUEST_TIMEOUT), - MAX_CONNECTION_POOL_SIZE(Rdf4jOntoDriverProperties.MAX_CONNECTION_POOL_SIZE); + MAX_CONNECTION_POOL_SIZE(Rdf4jOntoDriverProperties.MAX_CONNECTION_POOL_SIZE), + TRANSACTION_ISOLATION_LEVEL(Rdf4jOntoDriverProperties.TRANSACTION_ISOLATION_LEVEL); private final String name; diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jOntoDriverProperties.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jOntoDriverProperties.java index b7468ec15..f25c222e9 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jOntoDriverProperties.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jOntoDriverProperties.java @@ -127,6 +127,13 @@ public abstract class Rdf4jOntoDriverProperties { */ public static final String MAX_CONNECTION_POOL_SIZE = "cz.cvut.kbss.ontodriver.rdf4j.max-connections"; + /** + * RDF4J transaction isolation level. + * + * See {@link org.eclipse.rdf4j.common.transaction.IsolationLevels} for acceptable values. + */ + public static final String TRANSACTION_ISOLATION_LEVEL = "cz.cvut.kbss.ontodriver.rdf4j.transaction-isolation"; + private Rdf4jOntoDriverProperties() { throw new AssertionError(); } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/AbstractConnector.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/AbstractConnector.java deleted file mode 100644 index 8cb6976f1..000000000 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/AbstractConnector.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.ontodriver.rdf4j.connector; - -import cz.cvut.kbss.ontodriver.exception.OntoDriverException; -import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; -import cz.cvut.kbss.ontodriver.util.Transaction; - -abstract class AbstractConnector implements Connector { - - protected final Transaction transaction; - protected boolean open; - - protected AbstractConnector() { - this.transaction = new Transaction(); - } - - @Override - public boolean isOpen() { - return open; - } - - @Override - public void close() throws OntoDriverException { - if (!open) { - return; - } - this.open = false; - } - - @Override - public void begin() throws Rdf4jDriverException { - transaction.begin(); - } - - protected void verifyTransactionActive() { - if (!transaction.isActive()) { - throw new IllegalStateException(); - } - } -} diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectorFactory.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactory.java similarity index 82% rename from ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectorFactory.java rename to ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactory.java index 7d453d4f5..574506a41 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectorFactory.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactory.java @@ -21,16 +21,14 @@ import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import org.eclipse.rdf4j.repository.Repository; -public interface ConnectorFactory { +public interface ConnectionFactory { /** - * Creates a storage connector. - *

    - * It is possible that the underlying implementation will return some sort of proxy for a single storage connector. + * Creates a storage connection. * - * @return New storage connector + * @return New storage connection */ - Connector createStorageConnector(); + RepoConnection createStorageConnection(); /** * Closes this factory @@ -51,7 +49,7 @@ public interface ConnectorFactory { *

    * Note that this functionality is supported only for in-memory stores. * - * @param repository The new repository + * @param repository The new repository * @throws Rdf4jDriverException In case setting the repository fails */ void setRepository(Repository repository) throws Rdf4jDriverException; diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactoryConfig.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactoryConfig.java new file mode 100644 index 000000000..dd9289c9a --- /dev/null +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactoryConfig.java @@ -0,0 +1,11 @@ +package cz.cvut.kbss.ontodriver.rdf4j.connector; + +import org.eclipse.rdf4j.common.transaction.IsolationLevel; + +/** + * Configuration for the {@link ConnectionFactoryImpl}. + * @param isGraphDB Whether the underlying repository is GraphDB + * @param txIsolationLevel Configured transaction isolation level, possibly {@code null} + */ +public record ConnectionFactoryConfig(boolean isGraphDB, IsolationLevel txIsolationLevel) { +} diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectorFactoryImpl.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactoryImpl.java similarity index 59% rename from ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectorFactoryImpl.java rename to ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactoryImpl.java index 8f6bbf1e1..e3612259e 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectorFactoryImpl.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactoryImpl.java @@ -18,23 +18,36 @@ package cz.cvut.kbss.ontodriver.rdf4j.connector; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; import org.eclipse.rdf4j.repository.Repository; -public final class ConnectorFactoryImpl implements ConnectorFactory { +public final class ConnectionFactoryImpl implements ConnectionFactory { private boolean open; - private StorageConnector centralConnector; + private final StorageConnector connector; + private final boolean isGraphDB; + private final IsolationLevel txIsolationLevel; - public ConnectorFactoryImpl(StorageConnector connector) { + public ConnectionFactoryImpl(StorageConnector connector) { + this(connector, new ConnectionFactoryConfig(false, null)); + } + + public ConnectionFactoryImpl(StorageConnector connector, ConnectionFactoryConfig config) { this.open = true; - this.centralConnector = connector; + this.connector = connector; + this.isGraphDB = config.isGraphDB(); + this.txIsolationLevel = config.txIsolationLevel(); } @Override - public Connector createStorageConnector() { + public RepoConnection createStorageConnection() { ensureOpen(); - return new PoolingStorageConnector(centralConnector); + if (isGraphDB) { + return new GraphDBStorageConnection(connector, txIsolationLevel); + } else { + return new StorageConnection(connector, txIsolationLevel); + } } private void ensureOpen() { @@ -46,7 +59,7 @@ private void ensureOpen() { @Override public void setRepository(Repository repository) { ensureOpen(); - centralConnector.setRepository(repository); + connector.setRepository(repository); } @Override @@ -54,9 +67,8 @@ public synchronized void close() throws OntoDriverException { if (!open) { return; } - if (centralConnector != null) { - centralConnector.close(); - this.centralConnector = null; + if (connector != null) { + connector.close(); } this.open = false; } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/GraphDBStorageConnector.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/GraphDBStorageConnection.java similarity index 80% rename from ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/GraphDBStorageConnector.java rename to ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/GraphDBStorageConnection.java index ea418e898..d1a71383c 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/GraphDBStorageConnector.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/GraphDBStorageConnection.java @@ -17,8 +17,8 @@ */ package cz.cvut.kbss.ontodriver.rdf4j.connector; -import cz.cvut.kbss.ontodriver.rdf4j.connector.init.RepositoryConnectorInitializer; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.repository.RepositoryConnection; @@ -26,15 +26,15 @@ import java.util.Set; -public class GraphDBStorageConnector extends StorageConnector { +public class GraphDBStorageConnection extends StorageConnection { - public GraphDBStorageConnector(RepositoryConnectorInitializer repoInitializer) { - super(repoInitializer); + public GraphDBStorageConnection(StorageConnector storageConnector, IsolationLevel isolationLevel) { + super(storageConnector, isolationLevel); } @Override public boolean isInferred(Statement statement, Set contexts) throws Rdf4jDriverException { - try (final RepositoryConnection conn = acquireConnection()) { + try (final RepositoryConnection conn = storageConnector.acquireConnection()) { final IRI[] ctxArr = contexts.toArray(new IRI[0]); // Inferred statements are in the implicit graph in GraphDB. This graph is not accessible via the RDF4J API return conn.hasStatement(statement, true) && !conn.hasStatement(statement, false, ctxArr); diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/LocalModel.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/LocalModel.java deleted file mode 100644 index dd1c8c2f1..000000000 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/LocalModel.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.ontodriver.rdf4j.connector; - -import org.eclipse.rdf4j.model.IRI; -import org.eclipse.rdf4j.model.Model; -import org.eclipse.rdf4j.model.Resource; -import org.eclipse.rdf4j.model.Statement; -import org.eclipse.rdf4j.model.Value; -import org.eclipse.rdf4j.model.impl.LinkedHashModel; - -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Caches local transactional changes to the RDF4J repository model. - */ -class LocalModel { - - private final Model addedStatements; - private final Model removedStatements; - private final Set removedSubjectPredicateStatements; - - enum Contains { - TRUE, FALSE, UNKNOWN - } - - LocalModel() { - this.addedStatements = new LinkedHashModel(); - this.removedStatements = new LinkedHashModel(); - this.removedSubjectPredicateStatements = new HashSet<>(); - } - - List enhanceStatements(Stream statements, Resource subject, IRI property, - Value object, Collection context) { - final IRI[] ctxArray = context.toArray(new IRI[0]); - final Collection added = addedStatements.filter(subject, property, object, ctxArray); - final Collection removed = removedStatements.filter(subject, property, object, ctxArray); - final List result = statements.filter(s -> !removed.contains(s)) - .filter(s -> removedSubjectPredicateStatements.stream() - .noneMatch(spc -> spc.matches(s))) - .collect(Collectors.toList()); - result.addAll(added); - return result; - } - - Contains contains(Resource subject, IRI property, Value object, Set contexts) { - final IRI[] ctxArray = contexts.toArray(new IRI[0]); - if (addedStatements.contains(subject, property, object, ctxArray)) { - return Contains.TRUE; - } - return removedStatements.contains(subject, property, object, ctxArray) || - removedSubjectPredicateStatements.contains(new SubjectPredicateContext(subject, property, contexts)) ? Contains.FALSE : Contains.UNKNOWN; - } - - void addStatements(Collection statements) { - removedStatements.removeAll(statements); - addedStatements.addAll(statements); - } - - void removeStatements(Collection statements) { - addedStatements.removeAll(statements); - removedStatements.addAll(statements); - } - - void removePropertyValues(Collection toRemove) { - removedSubjectPredicateStatements.addAll(toRemove); - addedStatements.removeIf(s -> toRemove.stream().anyMatch(spc -> spc.matches(s))); - } - - Collection getAddedStatements() { - return addedStatements; - } - - Collection getRemovedStatements() { - return removedStatements; - } - - public Set getRemovedSubjectPredicateStatements() { - return removedSubjectPredicateStatements; - } -} diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/PoolingStorageConnector.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/PoolingStorageConnector.java deleted file mode 100644 index 9cf2bd487..000000000 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/PoolingStorageConnector.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.ontodriver.rdf4j.connector; - -import cz.cvut.kbss.ontodriver.exception.OntoDriverException; -import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; -import cz.cvut.kbss.ontodriver.rdf4j.query.QuerySpecification; -import org.eclipse.rdf4j.model.IRI; -import org.eclipse.rdf4j.model.Resource; -import org.eclipse.rdf4j.model.Statement; -import org.eclipse.rdf4j.model.Value; -import org.eclipse.rdf4j.model.ValueFactory; -import org.eclipse.rdf4j.query.TupleQueryResult; -import org.eclipse.rdf4j.repository.RepositoryConnection; -import org.eclipse.rdf4j.repository.RepositoryException; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.stream.Stream; - -public class PoolingStorageConnector extends AbstractConnector { - - private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock(); - private static final Lock READ = LOCK.readLock(); - private static final Lock WRITE = LOCK.writeLock(); - - private final StorageConnector centralConnector; - - private RepositoryConnection connection; - private LocalModel localModel; - - PoolingStorageConnector(StorageConnector centralConnector) { - this.centralConnector = centralConnector; - this.open = true; - } - - @Override - public TupleQueryResult executeSelectQuery(QuerySpecification query) throws Rdf4jDriverException { - if (transaction.isActive()) { - return new ConnectionStatementExecutor(wrapConnection()).executeSelectQuery(query); - } - READ.lock(); - try { - return centralConnector.executeSelectQuery(query); - } finally { - READ.unlock(); - } - } - - private RepositoryConnection wrapConnection() { - return new TransactionalRepositoryConnection(connection); - } - - @Override - public boolean executeBooleanQuery(QuerySpecification query) throws Rdf4jDriverException { - if (transaction.isActive()) { - return new ConnectionStatementExecutor(wrapConnection()).executeBooleanQuery(query); - } - READ.lock(); - try { - return centralConnector.executeBooleanQuery(query); - } finally { - READ.unlock(); - } - } - - @Override - public void executeUpdate(QuerySpecification query) throws Rdf4jDriverException { - WRITE.lock(); - try { - centralConnector.executeUpdate(query); - } finally { - WRITE.unlock(); - } - } - - @Override - public List getContexts() throws Rdf4jDriverException { - READ.lock(); - try { - return centralConnector.getContexts(); - } finally { - READ.unlock(); - } - } - - @Override - public ValueFactory getValueFactory() { - // We don't need to lock the central connector, as getting the value - // factory does not require communication with the repository - return centralConnector.getValueFactory(); - } - - @Override - public void begin() throws Rdf4jDriverException { - super.begin(); - this.localModel = new LocalModel(); - this.connection = centralConnector.acquireConnection(); - } - - @Override - public void commit() throws Rdf4jDriverException { - transaction.commit(); - WRITE.lock(); - try { - centralConnector.begin(); - centralConnector.removeStatements(localModel.getRemovedStatements()); - centralConnector.removePropertyValues(localModel.getRemovedSubjectPredicateStatements()); - centralConnector.addStatements(localModel.getAddedStatements()); - centralConnector.commit(); - transaction.afterCommit(); - } catch (Rdf4jDriverException e) { - transaction.rollback(); - centralConnector.rollback(); - transaction.afterRollback(); - throw e; - } finally { - WRITE.unlock(); - centralConnector.releaseConnection(connection); - this.localModel = null; - } - } - - @Override - public void rollback() throws Rdf4jDriverException { - transaction.rollback(); - this.localModel = null; - centralConnector.releaseConnection(connection); - transaction.afterRollback(); - } - - @Override - public void close() throws OntoDriverException { - if (open && transaction.isActive()) { - this.localModel = null; - centralConnector.releaseConnection(connection); - } - super.close(); - } - - @Override - public void addStatements(Collection statements) { - verifyTransactionActive(); - assert statements != null; - localModel.addStatements(statements); - } - - @Override - public void removeStatements(Collection statements) { - verifyTransactionActive(); - assert statements != null; - localModel.removeStatements(statements); - } - - @Override - public void removePropertyValues(Collection spc) { - localModel.removePropertyValues(spc); - } - - @Override - public Collection findStatements(Resource subject, IRI property, Value value, boolean includeInferred) - throws Rdf4jDriverException { - return findStatements(subject, property, value, includeInferred, Collections.emptySet()); - } - - @Override - public Collection findStatements(Resource subject, IRI property, Value value, - boolean includeInferred, Set contexts) - throws Rdf4jDriverException { - verifyTransactionActive(); - try { - final Stream statements = connection - .getStatements(subject, property, value, includeInferred, - contexts.toArray(new IRI[0])).stream(); - return localModel.enhanceStatements(statements, subject, property, value, contexts); - } catch (RepositoryException e) { - rollback(); - throw new Rdf4jDriverException(e); - } - } - - @Override - public boolean containsStatement(Resource subject, IRI property, Value value, boolean includeInferred, - Set contexts) - throws Rdf4jDriverException { - verifyTransactionActive(); - try { - final LocalModel.Contains containsLocally = localModel.contains(subject, property, value, contexts); - switch (containsLocally) { - case TRUE: - return true; - case FALSE: - return false; - default: - return connection - .hasStatement(subject, property, value, includeInferred, contexts.toArray(new IRI[0])); - } - } catch (RepositoryException e) { - rollback(); - throw new Rdf4jDriverException(e); - } - } - - @Override - public boolean isInferred(Statement statement, Set contexts) throws Rdf4jDriverException { - verifyTransactionActive(); - return centralConnector.isInferred(statement, contexts); - } - - @Override - public T unwrap(Class cls) throws OntoDriverException { - if (cls.isAssignableFrom(this.getClass())) { - return cls.cast(this); - } - return centralConnector.unwrap(cls); - } -} diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/Connector.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/RepoConnection.java similarity index 98% rename from ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/Connector.java rename to ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/RepoConnection.java index f17cfe68b..f968091ae 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/Connector.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/RepoConnection.java @@ -33,7 +33,7 @@ /** * A RDF4J repository connection wrapper. */ -public interface Connector extends Closeable, StatementExecutor, Wrapper { +public interface RepoConnection extends Closeable, StatementExecutor, Wrapper { /** * Explicitly starts a transaction. diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnection.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnection.java new file mode 100644 index 000000000..30a53f829 --- /dev/null +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnection.java @@ -0,0 +1,266 @@ +/* + * JOPA + * Copyright (C) 2023 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.ontodriver.rdf4j.connector; + +import cz.cvut.kbss.ontodriver.exception.OntoDriverException; +import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; +import cz.cvut.kbss.ontodriver.rdf4j.query.QuerySpecification; +import cz.cvut.kbss.ontodriver.rdf4j.util.ThrowingFunction; +import org.eclipse.rdf4j.common.transaction.IsolationLevel; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.RepositoryException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class StorageConnection implements RepoConnection { + + private static final Logger LOG = LoggerFactory.getLogger(StorageConnection.class); + + private boolean open; + + final StorageConnector storageConnector; + private final IsolationLevel isolationLevel; + private RepositoryConnection connection; + + public StorageConnection(StorageConnector storageConnector, IsolationLevel isolationLevel) { + this.storageConnector = storageConnector; + this.isolationLevel = isolationLevel; + this.open = true; + } + + @Override + public boolean isOpen() { + return open; + } + + @Override + public void close() throws Rdf4jDriverException { + if (!open) { + return; + } + LOG.debug("Closing repository connector."); + try { + if (connection != null) { + if (connection.isActive()) { + connection.rollback(); + } + connection.close(); + } + } catch (RepositoryException e) { + throw new Rdf4jDriverException("Exception caught when closing RDF4J repository connection.", e); + } finally { + this.open = false; + } + } + + @Override + public TupleQueryResult executeSelectQuery(QuerySpecification query) throws Rdf4jDriverException { + // Always create a separate connection, it is released by the result set once it is closed + return new ConnectionStatementExecutor(storageConnector.acquireConnection()).executeSelectQuery(query); + } + + @Override + public boolean executeBooleanQuery(QuerySpecification query) throws Rdf4jDriverException { + return withConnection((conn) -> new ConnectionStatementExecutor(conn).executeBooleanQuery(query)); + } + + private R withConnection(ThrowingFunction call) throws Rdf4jDriverException { + if (connection != null) { + return call.apply(connection); + } else { + try (final RepositoryConnection conn = storageConnector.acquireConnection()) { + return call.apply(conn); + } + } + } + + @Override + public void executeUpdate(QuerySpecification query) throws Rdf4jDriverException { + if (connection != null) { + new ConnectionStatementExecutor(connection).executeUpdate(query); + } else { + try (final RepositoryConnection conn = storageConnector.acquireConnection()) { + new ConnectionStatementExecutor(conn).executeUpdate(query); + } + } + } + + @Override + public List getContexts() throws Rdf4jDriverException { + return withConnection(conn -> { + try { + return connection.getContextIDs().stream().collect(Collectors.toList()); + } catch (RepositoryException e) { + throw new Rdf4jDriverException(e); + } + }); + } + + @Override + public ValueFactory getValueFactory() { + return storageConnector.getValueFactory(); + } + + @Override + public void begin() throws Rdf4jDriverException { + this.connection = storageConnector.acquireConnection(); + try { + connection.begin(isolationLevel); + } catch (RepositoryException e) { + throw new Rdf4jDriverException(e); + } + } + + @Override + public void commit() throws Rdf4jDriverException { + assert connection != null; + + try { + connection.commit(); + connection.close(); + this.connection = null; + } catch (RepositoryException e) { + throw new Rdf4jDriverException(e); + } + } + + @Override + public void rollback() throws Rdf4jDriverException { + assert connection != null; + try { + connection.rollback(); + connection.close(); + this.connection = null; + } catch (RepositoryException e) { + throw new Rdf4jDriverException(e); + } + } + + @Override + public void addStatements(Collection statements) throws Rdf4jDriverException { + verifyTransactionActive(); + assert connection != null; + + try { + connection.add(statements); + } catch (RepositoryException e) { + throw new Rdf4jDriverException(e); + } + } + + protected void verifyTransactionActive() { + if (connection == null || !connection.isActive()) { + throw new IllegalStateException(); + } + } + + @Override + public void removeStatements(Collection statements) throws Rdf4jDriverException { + verifyTransactionActive(); + assert connection != null; + + try { + connection.remove(statements); + } catch (RepositoryException e) { + throw new Rdf4jDriverException(e); + } + } + + @Override + public void removePropertyValues(Collection spc) throws Rdf4jDriverException { + verifyTransactionActive(); + assert connection != null; + + try { + spc.forEach(spcItem -> connection.remove(spcItem.getSubject(), spcItem.getPredicate(), null, spcItem.getContexts() + .toArray(Resource[]::new))); + } catch (RepositoryException e) { + throw new Rdf4jDriverException(e); + } + } + + @Override + public Collection findStatements(Resource subject, IRI property, Value value, boolean includeInferred) + throws Rdf4jDriverException { + return findStatements(subject, property, value, includeInferred, Collections.emptySet()); + } + + @Override + public Collection findStatements(Resource subject, org.eclipse.rdf4j.model.IRI property, + Value value, boolean includeInferred, Set context) + throws Rdf4jDriverException { + return withConnection(conn -> { + try { + return conn.getStatements(subject, property, null, includeInferred, context.toArray(new IRI[0])) + .stream() + .collect(Collectors.toList()); + } catch (RepositoryException e) { + throw new Rdf4jDriverException(e); + } + }); + } + + @Override + public boolean containsStatement(Resource subject, IRI property, Value value, boolean includeInferred, + Set contexts) throws Rdf4jDriverException { + assert contexts != null; + return withConnection(conn -> { + try { + return conn.hasStatement(subject, property, value, includeInferred, contexts.toArray(new IRI[0])); + } catch (RepositoryException e) { + throw new Rdf4jDriverException(e); + } + }); + } + + @Override + public boolean isInferred(Statement statement, Set contexts) throws Rdf4jDriverException { + assert contexts != null; + return withConnection(conn -> { + try { + final IRI[] ctxArr = contexts.toArray(new IRI[0]); + return conn.hasStatement(statement, true, ctxArr) && !connection.hasStatement(statement, false, ctxArr); + } catch (RepositoryException e) { + throw new Rdf4jDriverException(e); + } + }); + } + + @Override + public T unwrap(Class cls) throws OntoDriverException { + if (cls.isAssignableFrom(getClass())) { + return cls.cast(this); + } + if (cls.isAssignableFrom(RepositoryConnection.class)) { + return cls.cast(connection); + } + return storageConnector.unwrap(cls); + } +} diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnector.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnector.java index 9659e4498..8d19a6dc6 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnector.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnector.java @@ -17,270 +17,287 @@ */ package cz.cvut.kbss.ontodriver.rdf4j.connector; +import cz.cvut.kbss.ontodriver.Closeable; import cz.cvut.kbss.ontodriver.Wrapper; +import cz.cvut.kbss.ontodriver.config.DriverConfiguration; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; -import cz.cvut.kbss.ontodriver.rdf4j.connector.init.RepositoryConnectorInitializer; +import cz.cvut.kbss.ontodriver.rdf4j.config.Constants; +import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; +import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jOntoDriverProperties; +import cz.cvut.kbss.ontodriver.rdf4j.connector.init.RemoteRepositoryWrapper; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; -import cz.cvut.kbss.ontodriver.rdf4j.query.QuerySpecification; -import org.eclipse.rdf4j.model.IRI; +import cz.cvut.kbss.ontodriver.rdf4j.exception.RepositoryCreationException; +import cz.cvut.kbss.ontodriver.rdf4j.exception.RepositoryNotFoundException; +import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.Resource; -import org.eclipse.rdf4j.model.Statement; -import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.ValueFactory; -import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.model.vocabulary.CONFIG; +import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.RepositoryException; +import org.eclipse.rdf4j.repository.config.RepositoryConfig; +import org.eclipse.rdf4j.repository.config.RepositoryConfigException; +import org.eclipse.rdf4j.repository.config.RepositoryConfigSchema; +import org.eclipse.rdf4j.repository.http.HTTPRepository; +import org.eclipse.rdf4j.repository.manager.RemoteRepositoryManager; import org.eclipse.rdf4j.repository.manager.RepositoryManager; +import org.eclipse.rdf4j.repository.manager.RepositoryProvider; import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.config.SailRepositoryConfig; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.rio.Rio; import org.eclipse.rdf4j.sail.Sail; +import org.eclipse.rdf4j.sail.config.SailImplConfig; import org.eclipse.rdf4j.sail.helpers.SailWrapper; +import org.eclipse.rdf4j.sail.inferencer.fc.SchemaCachingRDFSInferencer; +import org.eclipse.rdf4j.sail.inferencer.fc.config.SchemaCachingRDFSInferencerConfig; import org.eclipse.rdf4j.sail.memory.MemoryStore; +import org.eclipse.rdf4j.sail.nativerdf.config.NativeStoreConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; import java.util.Objects; +import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; -public class StorageConnector extends AbstractConnector { +public class StorageConnector implements Closeable, Wrapper { private static final Logger LOG = LoggerFactory.getLogger(StorageConnector.class); + private static final String[] KNOWN_REMOTE_SCHEMES = {"http", "https", "ftp"}; + private static final String LOCAL_NATIVE_REPO = "repositories/"; + private static final String FILE_SCHEME = "file"; + private static final String CLASSPATH_PREFIX = "classpath:"; + + private final DriverConfiguration configuration; private final int maxReconnectAttempts; + private RepositoryManager manager; private Repository repository; - private final RepositoryManager manager; - private RepositoryConnection connection; - public StorageConnector(RepositoryConnectorInitializer repoInitializer) { - this.repository = repoInitializer.getRepository(); - this.manager = repoInitializer.getManager(); - this.maxReconnectAttempts = repoInitializer.getMaxReconnectAttempts(); - this.open = true; + private boolean open; + + public StorageConnector(DriverConfiguration configuration) throws Rdf4jDriverException { + this.configuration = configuration; + this.maxReconnectAttempts = resolveMaxReconnectAttempts(); } - @Override - public void close() throws Rdf4jDriverException { - if (!open) { - return; - } - LOG.debug("Closing repository connector."); + private int resolveMaxReconnectAttempts() throws Rdf4jDriverException { try { - repository.shutDown(); - if (manager != null) { - manager.shutDown(); + final int attempts = configuration.isSet(Rdf4jConfigParam.RECONNECT_ATTEMPTS) ? Integer.parseInt( + configuration.getProperty(Rdf4jConfigParam.RECONNECT_ATTEMPTS)) : + Constants.DEFAULT_RECONNECT_ATTEMPTS_COUNT; + if (attempts < 0) { + throw invalidReconnectAttemptsConfig(); } - } catch (RepositoryException e) { - throw new Rdf4jDriverException("Exception caught when closing RDF4J repository connection.", e); - } finally { - this.open = false; + return attempts; + } catch (NumberFormatException e) { + throw invalidReconnectAttemptsConfig(); } } - @Override - public TupleQueryResult executeSelectQuery(QuerySpecification query) throws Rdf4jDriverException { - final RepositoryConnection conn = acquireConnection(); - return new ConnectionStatementExecutor(conn).executeSelectQuery(query); - // The connection is released by the result set once it is closed - } - - RepositoryConnection acquireConnection() throws Rdf4jDriverException { - // Workaround for local native storage being reset when multiple drivers access it - if (!repository.isInitialized()) { - repository.init(); - } - LOG.trace("Acquiring repository connection."); - return acquire(1); + private static Rdf4jDriverException invalidReconnectAttemptsConfig() { + return new Rdf4jDriverException( + "Invalid value of configuration parameter " + Rdf4jOntoDriverProperties.RECONNECT_ATTEMPTS + + ". Must be a non-negative integer."); } - private RepositoryConnection acquire(int attempts) throws Rdf4jDriverException { + public void initializeRepository() throws Rdf4jDriverException { + final URI serverUri = configuration.getStorageProperties().getPhysicalURI(); + LOG.debug("Initializing connector to repository at {}", serverUri); try { - return repository.getConnection(); - } catch (RepositoryException e) { - if (attempts < maxReconnectAttempts) { - LOG.warn("Unable to acquire repository connection. Error is: {}. Retrying...", e.getMessage()); - return acquire(attempts + 1); + final boolean isRemote = isRemoteRepository(serverUri); + if (isRemote) { + this.repository = connectToRemoteRepository(serverUri.toString()); + } else { + this.repository = createLocalRepository(); } - LOG.error("Threshold of failed connection acquisition attempts reached, throwing exception."); - throw new Rdf4jDriverException(e); + verifyRepositoryCreated(serverUri, isRemote); + repository.init(); + } catch (RepositoryException | RepositoryConfigException e) { + throw new Rdf4jDriverException("Failed to acquire RDF4J repository connection.", e); } + this.open = true; } - void releaseConnection(RepositoryConnection conn) throws Rdf4jDriverException { - try { - if (conn != null) { - LOG.trace("Releasing repository connection."); - conn.close(); + private static boolean isRemoteRepository(URI uri) { + final String scheme = uri.getScheme(); + for (String s : KNOWN_REMOTE_SCHEMES) { + if (s.equals(scheme)) { + return true; } - } catch (RepositoryException e) { - throw new Rdf4jDriverException(e); } + return false; } - @Override - public boolean executeBooleanQuery(QuerySpecification query) throws Rdf4jDriverException { - try (final RepositoryConnection conn = acquireConnection()) { - return new ConnectionStatementExecutor(conn).executeBooleanQuery(query); + private Repository connectToRemoteRepository(String repoUri) { + this.manager = RepositoryProvider.getRepositoryManagerOfRepository(repoUri); + final RemoteRepositoryManager remoteManager = (RemoteRepositoryManager) manager; + final String username = configuration.getStorageProperties().getUsername(); + if (username != null) { + final String password = configuration.getStorageProperties().getPassword(); + remoteManager.setUsernameAndPassword(username, password); } + return connectToRemote(repoUri, 1); } - @Override - public void executeUpdate(QuerySpecification query) throws Rdf4jDriverException { - try (final RepositoryConnection conn = acquireConnection()) { - new ConnectionStatementExecutor(conn).executeUpdate(query); + private Repository connectToRemote(String repoUri, int attempts) { + try { + return new RemoteRepositoryWrapper((HTTPRepository) manager.getRepository(RepositoryProvider.getRepositoryIdOfRepository(repoUri)), configuration); + } catch (RepositoryException e) { + if (attempts < maxReconnectAttempts) { + LOG.warn("Unable to connect to repository {}. Error is: {}. Retrying...", repoUri, e.getMessage()); + return connectToRemote(repoUri, attempts + 1); + } + LOG.error("Threshold of failed connection attempts reached, throwing exception."); + throw e; } } - @Override - public List getContexts() throws Rdf4jDriverException { - try (final RepositoryConnection conn = acquireConnection()) { - return conn.getContextIDs().stream().collect(Collectors.toList()); - } catch (RepositoryException e) { - throw new Rdf4jDriverException(e); + private Repository createLocalRepository() { + if (configuration.isSet(Rdf4jConfigParam.REPOSITORY_CONFIG)) { + return createRepositoryFromConfig(); + } + final URI localUri = configuration.getStorageProperties().getPhysicalURI(); + if (!isFileUri(localUri) && configuration.is(Rdf4jConfigParam.USE_VOLATILE_STORAGE)) { + return createInMemoryRepository(); + } else { + return createNativeRepository(configuration, localUri.toString()); } } - @Override - public ValueFactory getValueFactory() { - return repository.getValueFactory(); + private Repository createRepositoryFromConfig() { + LOG.trace("Creating local repository from repository config file."); + final RepositoryConfig repoConfig = loadRepositoryConfig(); + this.manager = RepositoryProvider.getRepositoryManager(getRepositoryManagerBaseDir().orElse("")); + manager.addRepositoryConfig(repoConfig); + return manager.getRepository(getRepositoryId()); } - @Override - public void begin() throws Rdf4jDriverException { - super.begin(); - this.connection = acquireConnection(); - try { - connection.begin(); - } catch (RepositoryException e) { - transaction.rollback(); - throw new Rdf4jDriverException(e); + @SuppressWarnings("deprecated") + private RepositoryConfig loadRepositoryConfig() { + try (final InputStream is = getConfigFileContent()) { + final Model configModel = Rio.parse(is, "", RDFFormat.TURTLE); + Set resources = + configModel.filter(null, RDF.TYPE, CONFIG.Rep.Repository).subjects(); + if (resources.isEmpty()) { + // Support for legacy repository configuration vocabulary. + // https://rdf4j.org/documentation/reference/configuration/#migrating-old-configurations + resources = configModel.filter(null, RDF.TYPE, RepositoryConfigSchema.REPOSITORY).subjects(); + } + assert resources.size() == 1; + return RepositoryConfig.create(configModel, resources.iterator().next()); + } catch (IOException e) { + throw new RepositoryCreationException("Unable to create repository from the specified configuration.", e); } } - @Override - public void commit() throws Rdf4jDriverException { - assert connection != null; - - transaction.commit(); - try { - connection.commit(); - connection.close(); - this.connection = null; - transaction.afterCommit(); - } catch (RepositoryException e) { - transaction.rollback(); - throw new Rdf4jDriverException(e); + private InputStream getConfigFileContent() { + final String configPath = configuration.getProperty(Rdf4jConfigParam.REPOSITORY_CONFIG); + LOG.trace("Loading repository configuration file content from {}.", configPath); + if (configPath.startsWith(CLASSPATH_PREFIX)) { + final InputStream is = + getClass().getClassLoader().getResourceAsStream(configPath.substring(CLASSPATH_PREFIX.length())); + if (is == null) { + throw new RepositoryCreationException( + "Unable to find repository configuration file on classpath location " + configPath); + } + return is; + } else { + try { + return new FileInputStream(configPath); + } catch (FileNotFoundException e) { + throw new RepositoryCreationException("Unable to find repository configuration file at " + configPath, + e); + } } } - @Override - public void rollback() throws Rdf4jDriverException { - assert connection != null; - transaction.rollback(); - try { - connection.rollback(); - connection.close(); - this.connection = null; - } catch (RepositoryException e) { - throw new Rdf4jDriverException(e); - } finally { - transaction.afterRollback(); - } + private Optional getRepositoryManagerBaseDir() { + final String physicalUri = configuration.getStorageProperties().getPhysicalURI().toString(); + final String[] tmp = physicalUri.split(LOCAL_NATIVE_REPO); + return tmp.length == 2 ? Optional.of(tmp[0]) : Optional.empty(); } - @Override - public void addStatements(Collection statements) throws Rdf4jDriverException { - verifyTransactionActive(); - assert connection != null; - - try { - connection.add(statements); - } catch (RepositoryException e) { - throw new Rdf4jDriverException(e); + private String getRepositoryId() { + final String physicalUri = configuration.getStorageProperties().getPhysicalURI().toString(); + final String[] tmp = physicalUri.split(LOCAL_NATIVE_REPO); + if (tmp.length != 2) { + return physicalUri; } + String repoId = tmp[1]; + // Get rid of the trailing slash if necessary + return repoId.charAt(repoId.length() - 1) == '/' ? repoId.substring(0, repoId.length() - 1) : repoId; } - @Override - public void removeStatements(Collection statements) throws Rdf4jDriverException { - verifyTransactionActive(); - assert connection != null; - - try { - connection.remove(statements); - } catch (RepositoryException e) { - throw new Rdf4jDriverException(e); - } + private static boolean isFileUri(URI uri) { + return uri.getScheme() != null && uri.getScheme().equals(FILE_SCHEME); } - @Override - public void removePropertyValues(Collection spc) throws Rdf4jDriverException { - verifyTransactionActive(); - assert connection != null; - - try { - spc.forEach(spcItem -> connection.remove(spcItem.getSubject(), spcItem.getPredicate(), null, spcItem.getContexts() - .toArray(Resource[]::new))); - } catch (RepositoryException e) { - throw new Rdf4jDriverException(e); + /** + * Creates a local in-memory RDF4J repository which is disposed of when the VM shuts down. + */ + private Repository createInMemoryRepository() { + LOG.trace("Creating local in-memory repository."); + final MemoryStore ms = new MemoryStore(); + if (configuration.is(Rdf4jConfigParam.USE_INFERENCE)) { + return new SailRepository(new SchemaCachingRDFSInferencer(ms)); + } else { + return new SailRepository(ms); } } - @Override - public Collection findStatements(Resource subject, IRI property, Value value, boolean includeInferred) - throws Rdf4jDriverException { - return findStatements(subject, property, value, includeInferred, Collections.emptySet()); - } - - @Override - public Collection findStatements(Resource subject, org.eclipse.rdf4j.model.IRI property, - Value value, boolean includeInferred, Set context) - throws Rdf4jDriverException { - try (final RepositoryConnection conn = acquireConnection()) { - return conn.getStatements(subject, property, null, includeInferred, context.toArray(new IRI[0])).stream() - .collect(Collectors.toList()); - } catch (RepositoryException e) { - throw new Rdf4jDriverException(e); + /** + * Creates native repository. + *

    + * This kind of repository stores data in files and is persistent after the VM shuts down. + */ + private Repository createNativeRepository(DriverConfiguration configuration, String localUri) { + LOG.trace("Creating local native repository at " + localUri); + validateNativeStorePath(localUri); + try { + this.manager = RepositoryProvider.getRepositoryManagerOfRepository(localUri); + final String repoId = getRepositoryId(); + final RepositoryConfig cfg = createLocalNativeRepositoryConfig(repoId, configuration); + manager.addRepositoryConfig(cfg); + return manager.getRepository(repoId); + } catch (RepositoryConfigException | RepositoryException e) { + throw new RepositoryCreationException("Unable to create local repository at " + localUri, e); } } - @Override - public boolean containsStatement(Resource subject, IRI property, Value value, boolean includeInferred, - Set contexts) throws Rdf4jDriverException { - assert contexts != null; - try (final RepositoryConnection conn = acquireConnection()) { - return conn.hasStatement(subject, property, value, includeInferred, contexts.toArray(new IRI[0])); - } catch (RepositoryException e) { - throw new Rdf4jDriverException(e); + private static void validateNativeStorePath(String path) { + if (path.split(LOCAL_NATIVE_REPO).length != 2) { + throw new RepositoryCreationException( + "Unsupported local RDF4J repository path. Expected file://path/repositories/id but got " + + path); } } - @Override - public boolean isInferred(Statement statement, Set contexts) throws Rdf4jDriverException { - assert contexts != null; - try (final RepositoryConnection conn = acquireConnection()) { - final IRI[] ctxArr = contexts.toArray(new IRI[0]); - return conn.hasStatement(statement, true, ctxArr) && !conn.hasStatement(statement, false, ctxArr); - } catch (RepositoryException e) { - throw new Rdf4jDriverException(e); + private static RepositoryConfig createLocalNativeRepositoryConfig(String repoId, + DriverConfiguration configuration) { + SailImplConfig backend = new NativeStoreConfig(); + if (configuration.is(Rdf4jConfigParam.USE_INFERENCE)) { + backend = new SchemaCachingRDFSInferencerConfig(backend); } + final SailRepositoryConfig repoType = new SailRepositoryConfig(backend); + return new RepositoryConfig(repoId, repoType); } - @Override - public T unwrap(Class cls) throws OntoDriverException { - if (cls.isAssignableFrom(getClass())) { - return cls.cast(this); - } - if (cls.isAssignableFrom(repository.getClass())) { - return cls.cast(repository); - } - if (repository instanceof Wrapper) { - return ((Wrapper) repository).unwrap(cls); + private void verifyRepositoryCreated(URI serverUri, boolean isRemote) { + if (repository == null) { + if (isRemote) { + throw new RepositoryNotFoundException("Unable to reach repository at " + serverUri); + } else { + throw new RepositoryCreationException("Unable to create local repository at " + serverUri); + } } - throw new Rdf4jDriverException("No instance of class " + cls + " found."); } /** @@ -292,12 +309,10 @@ public T unwrap(Class cls) throws OntoDriverException { */ public void setRepository(Repository newRepository) { Objects.requireNonNull(newRepository); + verifyOpen(); if (!isInMemoryRepository(repository)) { throw new UnsupportedOperationException("Cannot replace repository which is not in-memory."); } - if (transaction.isActive()) { - throw new IllegalStateException("Cannot replace repository in transaction."); - } repository.shutDown(); assert newRepository.isInitialized(); this.repository = newRepository; @@ -314,4 +329,75 @@ private static boolean isInMemoryRepository(Repository repo) { } return sail instanceof MemoryStore; } + + public ValueFactory getValueFactory() { + verifyOpen(); + return repository.getValueFactory(); + } + + public RepositoryConnection acquireConnection() throws Rdf4jDriverException { + verifyOpen(); + // Workaround for local native storage being reset when multiple drivers access it + if (!repository.isInitialized()) { + repository.init(); + } + LOG.trace("Acquiring repository connection."); + return acquire(1); + } + + private RepositoryConnection acquire(int attempts) throws Rdf4jDriverException { + try { + return repository.getConnection(); + } catch (RepositoryException e) { + if (attempts < maxReconnectAttempts) { + LOG.warn("Unable to acquire repository connection. Error is: {}. Retrying...", e.getMessage()); + return acquire(attempts + 1); + } + LOG.error("Threshold of failed connection acquisition attempts reached, throwing exception."); + throw new Rdf4jDriverException(e); + } + } + + @Override + public void close() throws OntoDriverException { + if (!open) { + return; + } + try { + repository.shutDown(); + if (manager != null) { + manager.shutDown(); + } + } catch (RuntimeException e) { + throw new Rdf4jDriverException("Exception caught when closing repository connector.", e); + } finally { + this.open = false; + } + } + + @Override + public boolean isOpen() { + return open; + } + + private void verifyOpen() { + if (!open) { + throw new IllegalStateException("Connector is not open."); + } + } + + @Override + public T unwrap(Class cls) throws OntoDriverException { + verifyOpen(); + if (cls.isAssignableFrom(getClass())) { + return cls.cast(this); + } + if (cls.isAssignableFrom(repository.getClass())) { + return cls.cast(repository); + } + if (repository instanceof Wrapper) { + return ((Wrapper) repository).unwrap(cls); + } + throw new Rdf4jDriverException("No class of type " + cls + " found."); + } } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/FactoryOfFactories.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/FactoryOfFactories.java index 15ba1d50b..e058435a4 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/FactoryOfFactories.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/FactoryOfFactories.java @@ -19,79 +19,102 @@ import cz.cvut.kbss.ontodriver.config.DriverConfiguration; import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; -import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectorFactory; -import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectorFactoryImpl; -import cz.cvut.kbss.ontodriver.rdf4j.connector.GraphDBStorageConnector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectionFactory; +import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectionFactoryConfig; +import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectionFactoryImpl; import cz.cvut.kbss.ontodriver.rdf4j.connector.StorageConnector; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.loader.DefaultContextInferenceStatementLoaderFactory; import cz.cvut.kbss.ontodriver.rdf4j.loader.DefaultStatementLoaderFactory; import cz.cvut.kbss.ontodriver.rdf4j.loader.GraphDBStatementLoaderFactory; import cz.cvut.kbss.ontodriver.rdf4j.loader.StatementLoaderFactory; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.query.BooleanQuery; -import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; +import java.util.stream.Stream; /** * Builds factories for the driver. */ public class FactoryOfFactories { + private static final Logger LOG = LoggerFactory.getLogger(FactoryOfFactories.class); + /** * Property representing internal entity IDs in GraphDB. */ static final String GRAPHDB_INTERNAL_ID_PROPERTY = "http://www.ontotext.com/owlim/entity#id"; private final DriverConfiguration config; - private final RepositoryConnectorInitializer connectorInitializer; + private final StorageConnector connector; + + private final boolean isGraphDB; public FactoryOfFactories(DriverConfiguration config) throws Rdf4jDriverException { this.config = config; - this.connectorInitializer = new RepositoryConnectorInitializer(config); - connectorInitializer.initializeRepository(); + this.connector = new StorageConnector(config); + connector.initializeRepository(); + this.isGraphDB = isRepositoryGraphDB(); } - public ConnectorFactory createConnectorFactory() throws Rdf4jDriverException { - final StorageConnector connector; - if (isRepositoryGraphDB(connectorInitializer.getRepository())) { - connector = new GraphDBStorageConnector(connectorInitializer); - } else { - connector = new StorageConnector(connectorInitializer); - } - return new ConnectorFactoryImpl(connector); + public ConnectionFactory createConnectorFactory() throws Rdf4jDriverException { + return new ConnectionFactoryImpl(connector, resolveFactoryConfig()); } - public StatementLoaderFactory createStatementLoaderFactory() throws Rdf4jDriverException { + public StatementLoaderFactory createStatementLoaderFactory() { if (config.is(Rdf4jConfigParam.INFERENCE_IN_DEFAULT_CONTEXT)) { return new DefaultContextInferenceStatementLoaderFactory(); } - if (isRepositoryGraphDB(connectorInitializer.getRepository())) { + if (isGraphDB) { return new GraphDBStatementLoaderFactory(); } return new DefaultStatementLoaderFactory(); } + ConnectionFactoryConfig resolveFactoryConfig() throws Rdf4jDriverException { + final String isolationLevelConfig = config.getProperty(Rdf4jConfigParam.TRANSACTION_ISOLATION_LEVEL); + if (isolationLevelConfig != null) { + final Optional optionalLevel = Stream.of(IsolationLevels.values()) + .filter(level -> level.toString() + .equals(isolationLevelConfig)) + .findAny(); + if (optionalLevel.isEmpty()) { + throw new Rdf4jDriverException("Unsupported transaction isolation level value '" + isolationLevelConfig + "'."); + } + LOG.debug("Configured to use RDF4J transaction isolation level '{}'.", optionalLevel.get()); + return new ConnectionFactoryConfig(isGraphDB, optionalLevel.get()); + } + return new ConnectionFactoryConfig(isGraphDB, null); + } + /** * Checks whether the underlying repository is in fact GraphDB. *

    - * It asks the repository if any subject has an internal GraphDB entity ID (represented by the {@link - * #GRAPHDB_INTERNAL_ID_PROPERTY}). Such a triple does not normally show in the data, but is accessing in GraphDB. - * If, for some reason, data stored in a non-GraphDB repository explicitly use these identifiers, this method will - * return false positive result. + * It asks the repository if any subject has an internal GraphDB entity ID (represented by the + * {@link #GRAPHDB_INTERNAL_ID_PROPERTY}). Such a triple does not normally show in the data, but is accessing in + * GraphDB. If, for some reason, data stored in a non-GraphDB repository explicitly use these identifiers, this + * method will return false positive result. * - * @param repository RDF4J repository * @return {@code true} if repository is determined to be GraphDB, {@code false} otherwise */ - static boolean isRepositoryGraphDB(Repository repository) throws Rdf4jDriverException { + private boolean isRepositoryGraphDB() throws Rdf4jDriverException { try { - try (final RepositoryConnection connection = repository.getConnection()) { + try (final RepositoryConnection connection = connector.acquireConnection()) { final ValueFactory vf = connection.getValueFactory(); // Have to use a SPARQL query, because RDF4J API hasStatement call ended with an error // See https://graphdb.ontotext.com/documentation/standard/query-behaviour.html#how-to-access-internal-identifiers-for-entities final BooleanQuery query = connection.prepareBooleanQuery("ASK { ?x ?internalId ?y }"); query.setBinding("internalId", vf.createIRI(GRAPHDB_INTERNAL_ID_PROPERTY)); - return query.evaluate(); + final boolean result = query.evaluate(); + if (result) { + LOG.debug("Underlying repository is GraphDB."); + } + return result; } } catch (RuntimeException e) { throw new Rdf4jDriverException(e); diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/RepositoryConnectorInitializer.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/RepositoryConnectorInitializer.java deleted file mode 100644 index e437cd3ca..000000000 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/RepositoryConnectorInitializer.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.ontodriver.rdf4j.connector.init; - -import cz.cvut.kbss.ontodriver.config.DriverConfiguration; -import cz.cvut.kbss.ontodriver.rdf4j.config.Constants; -import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; -import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jOntoDriverProperties; -import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; -import cz.cvut.kbss.ontodriver.rdf4j.exception.RepositoryCreationException; -import cz.cvut.kbss.ontodriver.rdf4j.exception.RepositoryNotFoundException; -import org.eclipse.rdf4j.model.Model; -import org.eclipse.rdf4j.model.Resource; -import org.eclipse.rdf4j.model.vocabulary.CONFIG; -import org.eclipse.rdf4j.model.vocabulary.RDF; -import org.eclipse.rdf4j.repository.Repository; -import org.eclipse.rdf4j.repository.RepositoryException; -import org.eclipse.rdf4j.repository.config.RepositoryConfig; -import org.eclipse.rdf4j.repository.config.RepositoryConfigException; -import org.eclipse.rdf4j.repository.config.RepositoryConfigSchema; -import org.eclipse.rdf4j.repository.http.HTTPRepository; -import org.eclipse.rdf4j.repository.manager.RemoteRepositoryManager; -import org.eclipse.rdf4j.repository.manager.RepositoryManager; -import org.eclipse.rdf4j.repository.manager.RepositoryProvider; -import org.eclipse.rdf4j.repository.sail.SailRepository; -import org.eclipse.rdf4j.repository.sail.config.SailRepositoryConfig; -import org.eclipse.rdf4j.rio.RDFFormat; -import org.eclipse.rdf4j.rio.Rio; -import org.eclipse.rdf4j.sail.config.SailImplConfig; -import org.eclipse.rdf4j.sail.inferencer.fc.SchemaCachingRDFSInferencer; -import org.eclipse.rdf4j.sail.inferencer.fc.config.SchemaCachingRDFSInferencerConfig; -import org.eclipse.rdf4j.sail.memory.MemoryStore; -import org.eclipse.rdf4j.sail.nativerdf.config.NativeStoreConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.util.Optional; -import java.util.Set; - -public class RepositoryConnectorInitializer { - - private static final Logger LOG = LoggerFactory.getLogger(RepositoryConnectorInitializer.class); - - private static final String[] KNOWN_REMOTE_SCHEMES = {"http", "https", "ftp"}; - private static final String LOCAL_NATIVE_REPO = "repositories/"; - private static final String FILE_SCHEME = "file"; - private static final String CLASSPATH_PREFIX = "classpath:"; - - private final DriverConfiguration configuration; - private final int maxReconnectAttempts; - - private RepositoryManager manager; - private Repository repository; - - public RepositoryConnectorInitializer(DriverConfiguration configuration) throws Rdf4jDriverException { - this.configuration = configuration; - this.maxReconnectAttempts = resolveMaxReconnectAttempts(); - } - - private int resolveMaxReconnectAttempts() throws Rdf4jDriverException { - try { - final int attempts = configuration.isSet(Rdf4jConfigParam.RECONNECT_ATTEMPTS) ? Integer.parseInt( - configuration.getProperty(Rdf4jConfigParam.RECONNECT_ATTEMPTS)) : - Constants.DEFAULT_RECONNECT_ATTEMPTS_COUNT; - if (attempts < 0) { - throw invalidReconnectAttemptsConfig(); - } - return attempts; - } catch (NumberFormatException e) { - throw invalidReconnectAttemptsConfig(); - } - } - - private static Rdf4jDriverException invalidReconnectAttemptsConfig() { - return new Rdf4jDriverException( - "Invalid value of configuration parameter " + Rdf4jOntoDriverProperties.RECONNECT_ATTEMPTS + - ". Must be a non-negative integer."); - } - - public void initializeRepository() throws Rdf4jDriverException { - final URI serverUri = configuration.getStorageProperties().getPhysicalURI(); - LOG.debug("Initializing connector to repository at {}", serverUri); - try { - final boolean isRemote = isRemoteRepository(serverUri); - if (isRemote) { - this.repository = connectToRemoteRepository(serverUri.toString()); - } else { - this.repository = createLocalRepository(); - } - verifyRepositoryCreated(serverUri, isRemote); - repository.init(); - } catch (RepositoryException | RepositoryConfigException e) { - throw new Rdf4jDriverException("Failed to acquire RDF4J repository connection.", e); - } - } - - private static boolean isRemoteRepository(URI uri) { - final String scheme = uri.getScheme(); - for (String s : KNOWN_REMOTE_SCHEMES) { - if (s.equals(scheme)) { - return true; - } - } - return false; - } - - private Repository connectToRemoteRepository(String repoUri) { - this.manager = RepositoryProvider.getRepositoryManagerOfRepository(repoUri); - final RemoteRepositoryManager remoteManager = (RemoteRepositoryManager) manager; - final String username = configuration.getStorageProperties().getUsername(); - if (username != null) { - final String password = configuration.getStorageProperties().getPassword(); - remoteManager.setUsernameAndPassword(username, password); - } - return connectToRemote(repoUri, 1); - } - - private Repository connectToRemote(String repoUri, int attempts) { - try { - return new RemoteRepositoryWrapper((HTTPRepository) manager.getRepository(RepositoryProvider.getRepositoryIdOfRepository(repoUri)), configuration); - } catch (RepositoryException e) { - if (attempts < maxReconnectAttempts) { - LOG.warn("Unable to connect to repository {}. Error is: {}. Retrying...", repoUri, e.getMessage()); - return connectToRemote(repoUri, attempts + 1); - } - LOG.error("Threshold of failed connection attempts reached, throwing exception."); - throw e; - } - } - - private Repository createLocalRepository() { - if (configuration.isSet(Rdf4jConfigParam.REPOSITORY_CONFIG)) { - return createRepositoryFromConfig(); - } - final URI localUri = configuration.getStorageProperties().getPhysicalURI(); - if (!isFileUri(localUri) && configuration.is(Rdf4jConfigParam.USE_VOLATILE_STORAGE)) { - return createInMemoryRepository(); - } else { - return createNativeRepository(configuration, localUri.toString()); - } - } - - private Repository createRepositoryFromConfig() { - LOG.trace("Creating local repository from repository config file."); - final RepositoryConfig repoConfig = loadRepositoryConfig(); - this.manager = RepositoryProvider.getRepositoryManager(getRepositoryManagerBaseDir().orElse("")); - manager.addRepositoryConfig(repoConfig); - return manager.getRepository(getRepositoryId()); - } - - @SuppressWarnings("deprecated") - private RepositoryConfig loadRepositoryConfig() { - try (final InputStream is = getConfigFileContent()) { - final Model configModel = Rio.parse(is, "", RDFFormat.TURTLE); - Set resources = - configModel.filter(null, RDF.TYPE, CONFIG.Rep.Repository).subjects(); - if (resources.isEmpty()) { - // Support for legacy repository configuration vocabulary. - // https://rdf4j.org/documentation/reference/configuration/#migrating-old-configurations - resources = configModel.filter(null, RDF.TYPE, RepositoryConfigSchema.REPOSITORY).subjects(); - } - assert resources.size() == 1; - return RepositoryConfig.create(configModel, resources.iterator().next()); - } catch (IOException e) { - throw new RepositoryCreationException("Unable to create repository from the specified configuration.", e); - } - } - - private InputStream getConfigFileContent() { - final String configPath = configuration.getProperty(Rdf4jConfigParam.REPOSITORY_CONFIG); - LOG.trace("Loading repository configuration file content from {}.", configPath); - if (configPath.startsWith(CLASSPATH_PREFIX)) { - final InputStream is = - getClass().getClassLoader().getResourceAsStream(configPath.substring(CLASSPATH_PREFIX.length())); - if (is == null) { - throw new RepositoryCreationException( - "Unable to find repository configuration file on classpath location " + configPath); - } - return is; - } else { - try { - return new FileInputStream(configPath); - } catch (FileNotFoundException e) { - throw new RepositoryCreationException("Unable to find repository configuration file at " + configPath, - e); - } - } - } - - private Optional getRepositoryManagerBaseDir() { - final String physicalUri = configuration.getStorageProperties().getPhysicalURI().toString(); - final String[] tmp = physicalUri.split(LOCAL_NATIVE_REPO); - return tmp.length == 2 ? Optional.of(tmp[0]) : Optional.empty(); - } - - private String getRepositoryId() { - final String physicalUri = configuration.getStorageProperties().getPhysicalURI().toString(); - final String[] tmp = physicalUri.split(LOCAL_NATIVE_REPO); - if (tmp.length != 2) { - return physicalUri; - } - String repoId = tmp[1]; - // Get rid of the trailing slash if necessary - return repoId.charAt(repoId.length() - 1) == '/' ? repoId.substring(0, repoId.length() - 1) : repoId; - } - - private static boolean isFileUri(URI uri) { - return uri.getScheme() != null && uri.getScheme().equals(FILE_SCHEME); - } - - /** - * Creates a local in-memory RDF4J repository which is disposed of when the VM shuts down. - */ - private Repository createInMemoryRepository() { - LOG.trace("Creating local in-memory repository."); - final MemoryStore ms = new MemoryStore(); - if (configuration.is(Rdf4jConfigParam.USE_INFERENCE)) { - return new SailRepository(new SchemaCachingRDFSInferencer(ms)); - } else { - return new SailRepository(ms); - } - } - - /** - * Creates native repository. - *

    - * This kind of repository stores data in files and is persistent after the VM shuts down. - */ - private Repository createNativeRepository(DriverConfiguration configuration, String localUri) { - LOG.trace("Creating local native repository at " + localUri); - validateNativeStorePath(localUri); - try { - this.manager = RepositoryProvider.getRepositoryManagerOfRepository(localUri); - final String repoId = getRepositoryId(); - final RepositoryConfig cfg = createLocalNativeRepositoryConfig(repoId, configuration); - manager.addRepositoryConfig(cfg); - return manager.getRepository(repoId); - } catch (RepositoryConfigException | RepositoryException e) { - throw new RepositoryCreationException("Unable to create local repository at " + localUri, e); - } - } - - private static void validateNativeStorePath(String path) { - if (path.split(LOCAL_NATIVE_REPO).length != 2) { - throw new RepositoryCreationException( - "Unsupported local RDF4J repository path. Expected file://path/repositories/id but got " + - path); - } - } - - private static RepositoryConfig createLocalNativeRepositoryConfig(String repoId, - DriverConfiguration configuration) { - SailImplConfig backend = new NativeStoreConfig(); - if (configuration.is(Rdf4jConfigParam.USE_INFERENCE)) { - backend = new SchemaCachingRDFSInferencerConfig(backend); - } - final SailRepositoryConfig repoType = new SailRepositoryConfig(backend); - return new RepositoryConfig(repoId, repoType); - } - - private void verifyRepositoryCreated(URI serverUri, boolean isRemote) { - if (repository == null) { - if (isRemote) { - throw new RepositoryNotFoundException("Unable to reach repository at " + serverUri); - } else { - throw new RepositoryCreationException("Unable to create local repository at " + serverUri); - } - } - } - - public RepositoryManager getManager() { - return manager; - } - - public Repository getRepository() { - return repository; - } - - public int getMaxReconnectAttempts() { - return maxReconnectAttempts; - } -} diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/AbstractListIterator.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/AbstractListIterator.java index af0ff74a5..98abf6677 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/AbstractListIterator.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/AbstractListIterator.java @@ -19,7 +19,7 @@ import cz.cvut.kbss.ontodriver.descriptor.ListDescriptor; import cz.cvut.kbss.ontodriver.exception.IntegrityConstraintViolatedException; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.util.Rdf4jUtils; import cz.cvut.kbss.ontodriver.rdf4j.util.ValueConverter; import org.eclipse.rdf4j.model.IRI; @@ -40,12 +40,12 @@ abstract class AbstractListIterator implements ListIterator { protected final IRI context; protected final boolean includeInferred; - protected final Connector connector; + protected final RepoConnection connector; protected final ValueFactory vf; protected final ValueConverter valueConverter; - public AbstractListIterator(ListDescriptor listDescriptor, Connector connector, ValueFactory vf) { + public AbstractListIterator(ListDescriptor listDescriptor, RepoConnection connector, ValueFactory vf) { this.listOwner = Rdf4jUtils.toRdf4jIri(listDescriptor.getListOwner().getIdentifier(), vf); this.hasListProperty = Rdf4jUtils.toRdf4jIri(listDescriptor.getListProperty() .getIdentifier(), vf); diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ListHandler.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ListHandler.java index 7689e12ab..707875a2f 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ListHandler.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ListHandler.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.ontodriver.descriptor.ListDescriptor; import cz.cvut.kbss.ontodriver.descriptor.ListValueDescriptor; import cz.cvut.kbss.ontodriver.exception.IntegrityConstraintViolatedException; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.util.Rdf4jUtils; import org.eclipse.rdf4j.model.IRI; @@ -42,10 +42,10 @@ */ abstract class ListHandler> { - final Connector connector; + final RepoConnection connector; final ValueFactory vf; - ListHandler(Connector connector, ValueFactory vf) { + ListHandler(RepoConnection connector, ValueFactory vf) { this.connector = connector; this.vf = vf; } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListHandler.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListHandler.java index 80ddebeed..9e84a34b2 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListHandler.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListHandler.java @@ -21,7 +21,7 @@ import cz.cvut.kbss.ontodriver.descriptor.ReferencedListValueDescriptor; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.util.ValueConverter; import org.eclipse.rdf4j.model.IRI; @@ -29,12 +29,14 @@ import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.Set; public class ReferencedListHandler extends ListHandler> { @@ -43,7 +45,7 @@ public class ReferencedListHandler extends ListHandler createListRest(IRI headNode, ReferencedListValueDescri it.next(); while (it.hasNext()) { final Collection content = toRdf4jValue(listValueDescriptor.getNodeContent(), it.next()); - previous = createListNode(owner, hasNext, hasContent, content, context, previous, statements); + previous = appendListNode(owner, hasNext, hasContent, content, context, previous, statements); } + createNilTerminal(previous, hasNext, listValueDescriptor).ifPresent(statements::add); return statements; } - private IRI createListNode(IRI owner, IRI hasNext, IRI hasContent, Collection content, IRI context, + private IRI appendListNode(IRI owner, IRI hasNext, IRI hasContent, Collection content, IRI context, Resource previous, Collection statements) throws Rdf4jDriverException { final IRI node = generateSequenceNode(owner, context); @@ -125,6 +128,10 @@ private IRI createListNode(IRI owner, IRI hasNext, IRI hasContent, Collection createNilTerminal(Resource lastNode, IRI hasNext, ReferencedListDescriptor descriptor) { + return descriptor.isTerminatedByNil() ? Optional.of(vf.createStatement(lastNode, hasNext, RDF.NIL)) : Optional.empty(); + } + @Override protected void clearList(ReferencedListValueDescriptor listDescriptor) throws Rdf4jDriverException { final IRI hasNext = hasNext(listDescriptor); @@ -185,12 +192,20 @@ void appendNewNodes(ReferencedListValueDescriptor listDescriptor, MergeRe final IRI context = context(listDescriptor); assert i > 0; final Collection toAdd = new ArrayList<>((listDescriptor.getValues().size() - i) * 2); + if (listDescriptor.isTerminatedByNil()) { + removePreviousNilTerminal(previous, hasNext, context); + } while (i < listDescriptor.getValues().size()) { final Collection content = toRdf4jValue(listDescriptor.getNodeContent(), listDescriptor.getValues() .get(i)); - previous = createListNode(owner, hasNext, hasContent, content, context, previous, toAdd); + previous = appendListNode(owner, hasNext, hasContent, content, context, previous, toAdd); i++; } + createNilTerminal(previous, hasNext, listDescriptor).ifPresent(toAdd::add); connector.addStatements(toAdd); } + + private void removePreviousNilTerminal(Resource lastNode, IRI hasNext, IRI context) throws Rdf4jDriverException { + connector.removeStatements(Set.of(vf.createStatement(lastNode, hasNext, RDF.NIL, context))); + } } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListIterator.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListIterator.java index 97cc84bca..22231e7c3 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListIterator.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListIterator.java @@ -24,7 +24,7 @@ import cz.cvut.kbss.ontodriver.model.MultilingualString; import cz.cvut.kbss.ontodriver.model.NamedResource; import cz.cvut.kbss.ontodriver.model.Value; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.util.Rdf4jUtils; import org.eclipse.rdf4j.model.IRI; @@ -32,6 +32,7 @@ import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF; import java.util.ArrayList; import java.util.Collection; @@ -52,7 +53,7 @@ class ReferencedListIterator extends AbstractListIterator { private Collection currentContent; private Collection next; - public ReferencedListIterator(ReferencedListDescriptor listDescriptor, Connector connector, ValueFactory vf) + public ReferencedListIterator(ReferencedListDescriptor listDescriptor, RepoConnection connector, ValueFactory vf) throws Rdf4jDriverException { super(listDescriptor, connector, vf); this.listDescriptor = listDescriptor; @@ -67,7 +68,12 @@ private void init() throws Rdf4jDriverException { @Override public boolean hasNext() { - return !next.isEmpty(); + return !next.isEmpty() && !isNextNil(); + } + + private boolean isNextNil() { + assert next != null; + return RDF.NIL.equals(next.iterator().next().getObject()); } @Override diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListHandler.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListHandler.java index 11f629119..0c1d79e02 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListHandler.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListHandler.java @@ -21,7 +21,7 @@ import cz.cvut.kbss.ontodriver.descriptor.SimpleListValueDescriptor; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; @@ -36,7 +36,7 @@ public class SimpleListHandler extends ListHandler { - public SimpleListHandler(Connector connector, ValueFactory vf) { + public SimpleListHandler(RepoConnection connector, ValueFactory vf) { super(connector, vf); } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListIterator.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListIterator.java index e220d8c3e..31453fe70 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListIterator.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListIterator.java @@ -23,7 +23,7 @@ import cz.cvut.kbss.ontodriver.model.AxiomImpl; import cz.cvut.kbss.ontodriver.model.NamedResource; import cz.cvut.kbss.ontodriver.model.Value; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; @@ -44,7 +44,7 @@ class SimpleListIterator extends AbstractListIterator { private Statement current; private Collection next; - public SimpleListIterator(SimpleListDescriptor listDescriptor, Connector connector, ValueFactory vf) + public SimpleListIterator(SimpleListDescriptor listDescriptor, RepoConnection connector, ValueFactory vf) throws Rdf4jDriverException { super(listDescriptor, connector, vf); this.listDescriptor = listDescriptor; diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoader.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoader.java index ec3ffc323..cb6b4e92b 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoader.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoader.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.util.AxiomBuilder; import org.eclipse.rdf4j.model.Resource; @@ -39,7 +39,7 @@ */ public class DefaultContextInferenceStatementLoader extends StatementLoader { - public DefaultContextInferenceStatementLoader(Connector connector, Resource subject, AxiomBuilder axiomBuilder) { + public DefaultContextInferenceStatementLoader(RepoConnection connector, Resource subject, AxiomBuilder axiomBuilder) { super(connector, subject, axiomBuilder); } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoaderFactory.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoaderFactory.java index b96e854ea..dc3df509e 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoaderFactory.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoaderFactory.java @@ -17,14 +17,14 @@ */ package cz.cvut.kbss.ontodriver.rdf4j.loader; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.util.AxiomBuilder; import org.eclipse.rdf4j.model.Resource; public class DefaultContextInferenceStatementLoaderFactory implements StatementLoaderFactory { @Override - public StatementLoader create(Connector connector, Resource subject, AxiomBuilder axiomBuilder) { + public StatementLoader create(RepoConnection connector, Resource subject, AxiomBuilder axiomBuilder) { return new DefaultContextInferenceStatementLoader(connector, subject, axiomBuilder); } } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultStatementLoaderFactory.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultStatementLoaderFactory.java index 997f251cd..ad4f2d134 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultStatementLoaderFactory.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultStatementLoaderFactory.java @@ -17,14 +17,14 @@ */ package cz.cvut.kbss.ontodriver.rdf4j.loader; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.util.AxiomBuilder; import org.eclipse.rdf4j.model.Resource; public class DefaultStatementLoaderFactory implements StatementLoaderFactory { @Override - public StatementLoader create(Connector connector, Resource subject, AxiomBuilder axiomBuilder) { + public StatementLoader create(RepoConnection connector, Resource subject, AxiomBuilder axiomBuilder) { return new StatementLoader(connector, subject, axiomBuilder); } } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoader.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoader.java index 983deb3e9..e7a784700 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoader.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoader.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.util.AxiomBuilder; import org.eclipse.rdf4j.model.Resource; @@ -50,7 +50,7 @@ public class GraphDBStatementLoader extends StatementLoader { */ static final URI GRAPHDB_EXPLICIT_CONTEXT = URI.create("http://www.ontotext.com/explicit"); - public GraphDBStatementLoader(Connector connector, Resource subject, AxiomBuilder axiomBuilder) { + public GraphDBStatementLoader(RepoConnection connector, Resource subject, AxiomBuilder axiomBuilder) { super(connector, subject, axiomBuilder); } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoaderFactory.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoaderFactory.java index e35c7db20..3a2e6f6c7 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoaderFactory.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoaderFactory.java @@ -17,7 +17,7 @@ */ package cz.cvut.kbss.ontodriver.rdf4j.loader; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.util.AxiomBuilder; import org.eclipse.rdf4j.model.Resource; @@ -27,7 +27,7 @@ public class GraphDBStatementLoaderFactory implements StatementLoaderFactory { @Override - public StatementLoader create(Connector connector, Resource subject, AxiomBuilder axiomBuilder) { + public StatementLoader create(RepoConnection connector, Resource subject, AxiomBuilder axiomBuilder) { return new GraphDBStatementLoader(connector, subject, axiomBuilder); } } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/StatementLoader.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/StatementLoader.java index 4e8bbbbdf..193a88c14 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/StatementLoader.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/StatementLoader.java @@ -21,7 +21,7 @@ import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.rdf4j.config.Constants; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.util.AxiomBuilder; import cz.cvut.kbss.ontodriver.rdf4j.util.Rdf4jUtils; @@ -39,7 +39,7 @@ public class StatementLoader { - private final Connector connector; + private final RepoConnection connector; private final Resource subject; private final ValueFactory vf; private final AxiomBuilder axiomBuilder; @@ -48,7 +48,7 @@ public class StatementLoader { private boolean loadAll; boolean includeInferred; - public StatementLoader(Connector connector, Resource subject, AxiomBuilder axiomBuilder) { + public StatementLoader(RepoConnection connector, Resource subject, AxiomBuilder axiomBuilder) { this.connector = connector; this.vf = connector.getValueFactory(); this.subject = subject; diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/StatementLoaderFactory.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/StatementLoaderFactory.java index c4d0746b4..255d36ba3 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/StatementLoaderFactory.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/loader/StatementLoaderFactory.java @@ -17,11 +17,11 @@ */ package cz.cvut.kbss.ontodriver.rdf4j.loader; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.util.AxiomBuilder; import org.eclipse.rdf4j.model.Resource; public interface StatementLoaderFactory { - StatementLoader create(Connector connector, Resource subject, AxiomBuilder axiomBuilder); + StatementLoader create(RepoConnection connector, Resource subject, AxiomBuilder axiomBuilder); } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/util/ThrowingFunction.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/util/ThrowingFunction.java new file mode 100644 index 000000000..c216bd188 --- /dev/null +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/util/ThrowingFunction.java @@ -0,0 +1,23 @@ +package cz.cvut.kbss.ontodriver.rdf4j.util; + +import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; + +import java.util.function.Function; + +/** + * A {@link Function} that may throw a checked exception. + * + * @param Argument type + * @param Result type + */ +public interface ThrowingFunction { + + /** + * Apply the function to the specified argument. + * + * @param t Argument + * @return Result + * @throws Rdf4jDriverException If function evaluation throws an exception + */ + R apply(T t) throws Rdf4jDriverException; +} diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoaderTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoaderTest.java index 1613d8fe4..cc504c7b0 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoaderTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoaderTest.java @@ -27,7 +27,7 @@ import cz.cvut.kbss.ontodriver.rdf4j.config.Constants; import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; import cz.cvut.kbss.ontodriver.rdf4j.config.RuntimeConfiguration; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import cz.cvut.kbss.ontodriver.rdf4j.environment.TestRepositoryProvider; import org.eclipse.rdf4j.model.ValueFactory; @@ -54,7 +54,7 @@ class AxiomLoaderTest { private Generator.GeneratedData generatedData; private final ValueFactory vf = SimpleValueFactory.getInstance(); - private Connector connector; + private RepoConnection connector; private AxiomLoader axiomLoader; @@ -411,7 +411,7 @@ void loadsStringLiteralWithAllLanguagesWhenLanguageTagIsExplicitlySetToNull() th void loadAxiomsUsesSingleCallWhenLoadAllThresholdIsSetToLessThanAssertionCount() throws Exception { final DriverConfiguration driverConfig = new DriverConfiguration(TestRepositoryProvider.storageProperties()); driverConfig.setProperty(Rdf4jConfigParam.LOAD_ALL_THRESHOLD, Integer.toString(1)); - final Connector spiedConnector = spy(connector); + final RepoConnection spiedConnector = spy(connector); this.axiomLoader = new AxiomLoader(spiedConnector, new RuntimeConfiguration(driverConfig)); spiedConnector.begin(); diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoaderWithInferenceTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoaderWithInferenceTest.java index ea4f074c8..76bd8f64f 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoaderWithInferenceTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/AxiomLoaderWithInferenceTest.java @@ -23,7 +23,7 @@ import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; import cz.cvut.kbss.ontodriver.rdf4j.config.RuntimeConfiguration; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import cz.cvut.kbss.ontodriver.rdf4j.environment.TestRepositoryProvider; import org.eclipse.rdf4j.model.IRI; @@ -49,7 +49,7 @@ class AxiomLoaderWithInferenceTest { private final ValueFactory vf = SimpleValueFactory.getInstance(); - private Connector connector; + private RepoConnection connector; private AxiomLoader sut; diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/EpistemicAxiomRemoverTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/EpistemicAxiomRemoverTest.java index fd66a57ee..b97ab0f40 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/EpistemicAxiomRemoverTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/EpistemicAxiomRemoverTest.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.NamedResource; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.connector.SubjectPredicateContext; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import org.eclipse.rdf4j.model.ValueFactory; @@ -48,7 +48,7 @@ public class EpistemicAxiomRemoverTest { private AxiomDescriptor descriptor; @Mock - private Connector connectorMock; + private RepoConnection connectorMock; private ValueFactory vf; diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterTest.java index 7577a2e79..e20b5c160 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterTest.java @@ -29,7 +29,7 @@ import cz.cvut.kbss.ontodriver.model.Value; import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; import cz.cvut.kbss.ontodriver.rdf4j.config.RuntimeConfiguration; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.connector.SubjectPredicateContext; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; @@ -86,7 +86,7 @@ class Rdf4jAdapterTest { private static IRI subjectIri; @Mock - private Connector connectorMock; + private RepoConnection connectorMock; private Rdf4jAdapter adapter; diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterWithStoreTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterWithStoreTest.java index 156b05499..864563e11 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterWithStoreTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterWithStoreTest.java @@ -25,11 +25,10 @@ import cz.cvut.kbss.ontodriver.model.Value; import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; import cz.cvut.kbss.ontodriver.rdf4j.config.RuntimeConfiguration; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; -import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectorFactory; -import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectorFactoryImpl; +import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectionFactory; +import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectionFactoryImpl; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.connector.StorageConnector; -import cz.cvut.kbss.ontodriver.rdf4j.connector.init.RepositoryConnectorInitializer; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; @@ -56,8 +55,8 @@ public class Rdf4jAdapterWithStoreTest { private Repository repo; private ValueFactory vf; - private ConnectorFactory factory; - private Connector connector; + private ConnectionFactory factory; + private RepoConnection connector; private Rdf4jAdapter adapter; @BeforeEach @@ -66,10 +65,10 @@ public void setUp() throws Exception { .physicalUri("memory-store").build(); final DriverConfiguration configuration = new DriverConfiguration(sp); configuration.setProperty(Rdf4jConfigParam.USE_VOLATILE_STORAGE, Boolean.toString(true)); - final RepositoryConnectorInitializer connectorInitializer = new RepositoryConnectorInitializer(configuration); + final StorageConnector connectorInitializer = new StorageConnector(configuration); connectorInitializer.initializeRepository(); - this.factory = new ConnectorFactoryImpl(new StorageConnector(connectorInitializer)); - this.connector = factory.createStorageConnector(); + this.factory = new ConnectionFactoryImpl(connectorInitializer); + this.connector = factory.createStorageConnection(); this.adapter = new Rdf4jAdapter(connector, new RuntimeConfiguration(configuration)); this.repo = adapter.unwrap(Repository.class); this.vf = repo.getValueFactory(); @@ -103,8 +102,8 @@ public void persistIndividualInTwoClassesInIndependentTransactionsIsPossible() t try (RepositoryConnection connection = repo.getConnection()) { final Resource subj = vf.createIRI(SUBJECT.getIdentifier().toString()); - final List classAssertions = connection.getStatements(subj, RDF.TYPE, null, false).stream() - .collect(Collectors.toList()); + final List classAssertions = connection.getStatements(subj, RDF.TYPE, null, false) + .stream().toList(); final Set types = classAssertions.stream().map(s -> URI.create(s.getObject().stringValue())).collect( Collectors.toSet()); assertTrue(types.contains(tOne)); diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jDriverTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jDriverTest.java index 478afd2ff..868c3068f 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jDriverTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jDriverTest.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.ontodriver.Connection; import cz.cvut.kbss.ontodriver.OntologyStorageProperties; import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jOntoDriverProperties; -import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectorFactory; +import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectionFactory; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -55,13 +55,13 @@ public void setUp() throws Exception { @Test public void testClose() throws Exception { - final Field connectorFactoryField = Rdf4jDriver.class.getDeclaredField("connectorFactory"); + final Field connectorFactoryField = Rdf4jDriver.class.getDeclaredField("connectionFactory"); connectorFactoryField.setAccessible(true); - final ConnectorFactory connectorFactory = (ConnectorFactory) connectorFactoryField.get(driver); + final ConnectionFactory connectionFactory = (ConnectionFactory) connectorFactoryField.get(driver); assertTrue(driver.isOpen()); driver.close(); assertFalse(driver.isOpen()); - assertFalse(connectorFactory.isOpen()); + assertFalse(connectionFactory.isOpen()); } @Test diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jPropertiesTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jPropertiesTest.java index e311fb646..22da0c3d9 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jPropertiesTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jPropertiesTest.java @@ -24,7 +24,7 @@ import cz.cvut.kbss.ontodriver.model.Value; import cz.cvut.kbss.ontodriver.rdf4j.config.Constants; import cz.cvut.kbss.ontodriver.rdf4j.config.RuntimeConfiguration; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import cz.cvut.kbss.ontodriver.rdf4j.loader.StatementLoader; import cz.cvut.kbss.ontodriver.rdf4j.loader.StatementLoaderFactory; @@ -55,7 +55,7 @@ class Rdf4jPropertiesTest { private static final Assertion ASSERTION = Assertion.createDataPropertyAssertion(Generator.generateUri(), false); @Mock - private Connector connectorMock; + private RepoConnection connectorMock; @Mock private RuntimeConfiguration configMock; diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/TypesHandlerTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/TypesHandlerTest.java index 876ad13be..91a8d9e6b 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/TypesHandlerTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/TypesHandlerTest.java @@ -19,7 +19,7 @@ import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Statement; @@ -54,7 +54,7 @@ public class TypesHandlerTest { private static ValueFactory vf; @Mock - private Connector connectorMock; + private RepoConnection connectorMock; private TypesHandler handler; diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectorFactoryImplTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactoryImplTest.java similarity index 92% rename from ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectorFactoryImplTest.java rename to ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactoryImplTest.java index 345d84b0b..b5304fb3d 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectorFactoryImplTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/ConnectionFactoryImplTest.java @@ -25,12 +25,12 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; -public class ConnectorFactoryImplTest { +public class ConnectionFactoryImplTest { @Test public void setRepositoryThrowsIllegalStateWhenCalledOnClosedFactory() throws Exception { final StorageConnector connector = mock(StorageConnector.class); - final ConnectorFactory sut = new ConnectorFactoryImpl(connector); + final ConnectionFactory sut = new ConnectionFactoryImpl(connector); sut.close(); final Repository repo = new SailRepository(new MemoryStore()); try { diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/LocalModelTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/LocalModelTest.java deleted file mode 100644 index 5c469020d..000000000 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/LocalModelTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.ontodriver.rdf4j.connector; - -import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; -import org.eclipse.rdf4j.model.IRI; -import org.eclipse.rdf4j.model.Statement; -import org.eclipse.rdf4j.model.ValueFactory; -import org.eclipse.rdf4j.model.impl.SimpleValueFactory; -import org.junit.jupiter.api.Test; - -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class LocalModelTest { - - private final ValueFactory vf = SimpleValueFactory.getInstance(); - - private final LocalModel sut = new LocalModel(); - - @Test - public void containsReturnsTrueWhenStatementWasAddedInLocalModel() { - final IRI subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - sut.addStatements(Collections - .singletonList(vf.createStatement(subject, property, vf.createLiteral(117)))); - assertEquals(LocalModel.Contains.TRUE, sut.contains(subject, property, null, Collections.emptySet())); - } - - @Test - public void containsReturnsFalseWhenStatementWasRemovedInLocalModel() { - final IRI subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - sut.removeStatements(Collections - .singletonList(vf.createStatement(subject, property, vf.createLiteral(117)))); - assertEquals(LocalModel.Contains.FALSE, sut.contains(subject, property, null, Collections.emptySet())); - } - - @Test - void containsReturnsFalseWhenStatementSubjectAndPredicateWereRemovedInLocalModel() { - final IRI subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - sut.removePropertyValues(Set.of(new SubjectPredicateContext(subject, property, Collections.emptySet()))); - - assertEquals(LocalModel.Contains.FALSE, sut.contains(subject, property, null, Collections.emptySet())); - } - - @Test - public void containsReturnsUnknownWhenStatementsIsNotInLocalModel() { - final IRI subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - assertEquals(LocalModel.Contains.UNKNOWN, sut.contains(subject, property, null, Collections.emptySet())); - } - - @Test - public void containsReturnsTrueWhenStatementWasAddedInLocalModel_Context() { - final IRI subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - final IRI context = vf.createIRI(Generator.generateUri().toString()); - sut.addStatements(Collections.singletonList( - vf.createStatement(subject, property, vf.createLiteral(117), context))); - assertEquals(LocalModel.Contains.TRUE, - sut.contains(subject, property, null, Collections.singleton(context))); - } - - @Test - public void containsReturnsFalseWhenStatementWasRemovedInLocalModel_Context() { - final IRI subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - final IRI context = vf.createIRI(Generator.generateUri().toString()); - sut.removeStatements(Collections.singletonList( - vf.createStatement(subject, property, vf.createLiteral(117), context))); - assertEquals(LocalModel.Contains.FALSE, - sut.contains(subject, property, null, Collections.singleton(context))); - } - - @Test - public void containsReturnsUnknownWhenStatementsIsNotInLocalModel_Context() { - final IRI subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - final IRI context = vf.createIRI(Generator.generateUri().toString()); - assertEquals(LocalModel.Contains.UNKNOWN, - sut.contains(subject, property, null, Collections.singleton(context))); - } - - @Test - void enhanceStatementsRemovesStatementsWhoseSubjectPredicateAndContextMatchRemoved() { - final IRI subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - sut.removePropertyValues(Set.of(new SubjectPredicateContext(subject, property, Collections.emptySet()))); - final Stream stream = Stream.of( - vf.createStatement(subject, property, vf.createLiteral(1)), - vf.createStatement(subject, property, vf.createLiteral(2)), - vf.createStatement(subject, vf.createIRI(Generator.generateUri() - .toString()), vf.createLiteral("Three", "en")) - ); - - final List result = sut.enhanceStatements(stream, subject, property, null, Collections.emptySet()); - assertTrue(result.stream().noneMatch(s -> s.getSubject().equals(subject) && s.getPredicate().equals(property))); - } - - @Test - void removeStatementsBySubjectAndPredicateRemovesPreviouslyAddedStatementsWithMatchingSubjectPredicateAndContext() { - final IRI subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - final IRI context = vf.createIRI(Generator.generateUri().toString()); - sut.addStatements(Collections.singletonList( - vf.createStatement(subject, property, vf.createLiteral(117), context))); - sut.removePropertyValues(Set.of(new SubjectPredicateContext(subject, property, Set.of(context)))); - - assertTrue(sut.getAddedStatements().isEmpty()); - } -} diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/PoolingStorageConnectorTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/PoolingStorageConnectorTest.java deleted file mode 100644 index 697c0d34a..000000000 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/PoolingStorageConnectorTest.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * JOPA - * Copyright (C) 2023 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.ontodriver.rdf4j.connector; - -import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; -import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; -import cz.cvut.kbss.ontodriver.rdf4j.query.QuerySpecification; -import cz.cvut.kbss.ontodriver.util.Transaction; -import cz.cvut.kbss.ontodriver.util.TransactionState; -import org.eclipse.rdf4j.common.iteration.CloseableIteration; -import org.eclipse.rdf4j.model.IRI; -import org.eclipse.rdf4j.model.Resource; -import org.eclipse.rdf4j.model.Statement; -import org.eclipse.rdf4j.model.ValueFactory; -import org.eclipse.rdf4j.model.impl.SimpleValueFactory; -import org.eclipse.rdf4j.query.BooleanQuery; -import org.eclipse.rdf4j.query.QueryLanguage; -import org.eclipse.rdf4j.query.TupleQuery; -import org.eclipse.rdf4j.repository.RepositoryConnection; -import org.eclipse.rdf4j.repository.RepositoryException; -import org.eclipse.rdf4j.repository.RepositoryResult; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.reflect.Whitebox; - -import java.lang.reflect.Field; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.locks.Lock; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -@PrepareForTest(PoolingStorageConnector.class) -public class PoolingStorageConnectorTest { - - @Mock - private StorageConnector centralMock; - @Mock - private Lock readLock; - @Mock - private Lock writeLock; - - private ValueFactory vf; - - private Transaction transaction; - - private PoolingStorageConnector connector; - - @BeforeEach - public void setUp() throws Exception { - this.vf = SimpleValueFactory.getInstance(); - this.connector = new PoolingStorageConnector(centralMock); - final Field transactionField = AbstractConnector.class.getDeclaredField("transaction"); - transactionField.setAccessible(true); - this.transaction = (Transaction) transactionField.get(connector); - Whitebox.setInternalState(PoolingStorageConnector.class, "READ", readLock); - Whitebox.setInternalState(PoolingStorageConnector.class, "WRITE", writeLock); - } - - @Test - public void testBegin() throws Exception { - assertFalse(transaction.isActive()); - connector.begin(); - assertTrue(transaction.isActive()); - } - - @Test - public void executeSelectOutsideTransactionRunsOnCentralConnector() throws Exception { - final String query = "Some query"; - connector.executeSelectQuery(QuerySpecification.query(query)); - - InOrder inOrder = inOrder(readLock, centralMock); - inOrder.verify(readLock).lock(); - inOrder.verify(centralMock).executeSelectQuery(QuerySpecification.query(query)); - inOrder.verify(readLock).unlock(); - } - - @Test - public void executeSelectInTransactionRunsOnTransactionalRepositoryConnection() throws Exception { - final String query = "SELECT Some query"; - final RepositoryConnection conn = mock(RepositoryConnection.class); - final TupleQuery tq = mock(TupleQuery.class); - when(conn.prepareTupleQuery(QueryLanguage.SPARQL, query)).thenReturn(tq); - when(centralMock.acquireConnection()).thenReturn(conn); - connector.begin(); - connector.executeSelectQuery(QuerySpecification.query(query)); - - verify(conn).prepareTupleQuery(QueryLanguage.SPARQL, query); - verify(tq).evaluate(); - } - - @Test - public void testUnlockWhenExecuteQueryThrowsException() throws Exception { - final String query = "Some query"; - when(centralMock.executeSelectQuery(any())).thenThrow(new Rdf4jDriverException()); - try { - assertThrows(Rdf4jDriverException.class, () -> connector.executeSelectQuery(QuerySpecification.query(query))); - } finally { - verify(readLock).lock(); - verify(readLock).unlock(); - } - } - - @Test - public void executeBooleanQueryRunsOnCentralConnectionWhenNoTransactionIsActive() throws Exception { - final String query = "ASK some query"; - connector.executeBooleanQuery(QuerySpecification.query(query)); - - InOrder inOrder = inOrder(readLock, centralMock); - inOrder.verify(readLock).lock(); - inOrder.verify(centralMock).executeBooleanQuery(QuerySpecification.query(query)); - inOrder.verify(readLock).unlock(); - } - - @Test - public void executeBooleanRunsOnTransactionalConnectionWhenTransactionIsActive() throws Exception { - final String query = "ASK some query"; - final RepositoryConnection conn = mock(RepositoryConnection.class); - final BooleanQuery bq = mock(BooleanQuery.class); - when(conn.prepareBooleanQuery(QueryLanguage.SPARQL, query)).thenReturn(bq); - when(centralMock.acquireConnection()).thenReturn(conn); - connector.begin(); - connector.executeBooleanQuery(QuerySpecification.query(query)); - - verify(conn).prepareBooleanQuery(QueryLanguage.SPARQL, query); - verify(bq).evaluate(); - } - - @Test - public void unlocksReadLockWhenExecuteBooleanQueryThrowsException() throws Exception { - final String query = "ASK some query"; - when(centralMock.executeBooleanQuery(any())).thenThrow(new Rdf4jDriverException()); - - try { - assertThrows(Rdf4jDriverException.class, () -> connector.executeBooleanQuery(QuerySpecification.query(query))); - } finally { - verify(readLock).unlock(); - } - } - - @Test - public void testExecuteUpdate() throws Exception { - connector.begin(); - final String query = "Some query"; - connector.executeUpdate(QuerySpecification.query(query)); - - InOrder inOrder = inOrder(writeLock, centralMock); - inOrder.verify(writeLock).lock(); - inOrder.verify(centralMock).executeUpdate(QuerySpecification.query(query)); - inOrder.verify(writeLock).unlock(); - } - - @Test - public void testUnlockWhenExecuteUpdateThrowsException() throws Exception { - connector.begin(); - final String query = "Some query"; - doThrow(new Rdf4jDriverException()).when(centralMock).executeUpdate(any()); - try { - assertThrows(Rdf4jDriverException.class, () -> connector.executeUpdate(QuerySpecification.query(query))); - } finally { - verify(writeLock).unlock(); - } - } - - @Test - public void testGetContexts() throws Exception { - connector.getContexts(); - verify(readLock).lock(); - verify(centralMock).getContexts(); - verify(readLock).unlock(); - } - - @Test - public void testCommit() throws Exception { - connector.begin(); - connector.commit(); - verify(writeLock).lock(); - verify(centralMock).begin(); - verify(centralMock).addStatements(anyCollection()); - verify(centralMock).removeStatements(anyCollection()); - verify(centralMock).commit(); - verify(writeLock).unlock(); - assertFalse(transaction.isActive()); - } - - @Test - public void testUnlockWhenCommitThrowsException() throws Exception { - doThrow(new Rdf4jDriverException()).when(centralMock).commit(); - connector.begin(); - try { - assertThrows(Rdf4jDriverException.class, () -> connector.commit()); - } finally { - verify(centralMock).begin(); - verify(centralMock).addStatements(anyCollection()); - verify(centralMock).removeStatements(anyCollection()); - verify(centralMock).commit(); - verify(writeLock).unlock(); - assertEquals(TransactionState.ABORTED, transaction.getState()); - } - } - - @Test - public void testRollback() throws Exception { - connector.begin(); - connector.rollback(); - assertEquals(TransactionState.ABORTED, transaction.getState()); - } - - @Test - public void testAddStatementsInactiveTransaction() { - final List statements = getStatements(); - assertThrows(IllegalStateException.class, () -> connector.addStatements(statements)); - } - - private List getStatements() { - final Statement stmt = mock(Statement.class); - return Collections.singletonList(stmt); - } - - @Test - public void testRemoveStatementsInactiveTransaction() { - final List statements = getStatements(); - assertThrows(IllegalStateException.class, () -> connector.removeStatements(statements)); - } - - @Test - public void testClose() throws Exception { - assertTrue(connector.isOpen()); - connector.close(); - assertFalse(connector.isOpen()); - } - - @Test - public void unwrapReturnsItselfWhenClassMatches() throws Exception { - assertSame(connector, connector.unwrap(PoolingStorageConnector.class)); - } - - @Test - public void transactionBeginAcquiresRepositoryConnection() throws Exception { - connector.begin(); - verify(centralMock).acquireConnection(); - } - - @Test - public void transactionCommitReleasesRepositoryConnection() throws Exception { - final RepositoryConnection conn = mock(RepositoryConnection.class); - when(centralMock.acquireConnection()).thenReturn(conn); - connector.begin(); - connector.commit(); - InOrder order = inOrder(centralMock); - order.verify(centralMock).acquireConnection(); - order.verify(centralMock).releaseConnection(conn); - } - - @Test - public void transactionRollbackReleasesRepositoryConnection() throws Exception { - final RepositoryConnection conn = mock(RepositoryConnection.class); - when(centralMock.acquireConnection()).thenReturn(conn); - connector.begin(); - connector.rollback(); - InOrder order = inOrder(centralMock); - order.verify(centralMock).acquireConnection(); - order.verify(centralMock).releaseConnection(conn); - } - - @Test - public void findStatementsReusesRepositoryConnectionDuringTransaction() throws Exception { - final RepositoryConnection conn = mock(RepositoryConnection.class); - when(conn.getStatements(any(Resource.class), any(IRI.class), any(), anyBoolean())) - .thenReturn(new RepositoryResult(mock(CloseableIteration.class))); - when(centralMock.acquireConnection()).thenReturn(conn); - final Resource res = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - connector.begin(); - connector.findStatements(res, property, null, false); - verify(centralMock).acquireConnection(); - verify(conn).getStatements(res, property, null, false); - } - - @Test - public void exceptionInFindStatementsCausesTransactionRollback() throws Exception { - final RepositoryConnection conn = mock(RepositoryConnection.class); - when(conn.getStatements(any(Resource.class), any(IRI.class), any(), anyBoolean())) - .thenThrow(new RepositoryException()); - when(centralMock.acquireConnection()).thenReturn(conn); - final Resource res = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - final Connector spy = spy(connector); - doCallRealMethod().when(spy).begin(); - spy.begin(); - doCallRealMethod().when(spy).findStatements(any(Resource.class), any(IRI.class), any(), anyBoolean()); - try { - assertThrows(Rdf4jDriverException.class, () -> spy.findStatements(res, property, null, false)); - } finally { - verify(spy).rollback(); - } - } - - @Test - public void closeReleasesActiveConnection() throws Exception { - final RepositoryConnection conn = mock(RepositoryConnection.class); - when(centralMock.acquireConnection()).thenReturn(conn); - connector.begin(); - connector.close(); - verify(centralMock).releaseConnection(conn); - } - - @Test - public void repeatedCloseIsHandled() throws Exception { - final RepositoryConnection conn = mock(RepositoryConnection.class); - when(centralMock.acquireConnection()).thenReturn(conn); - connector.begin(); - connector.close(); - connector.close(); - verify(centralMock).releaseConnection(conn); - } - - @Test - public void containsReturnsTrueWhenLocalModelContainsStatement() throws Exception { - final Resource subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - final RepositoryConnection conn = mock(RepositoryConnection.class); - when(centralMock.acquireConnection()).thenReturn(conn); - connector.begin(); - connector - .addStatements(Collections.singletonList(vf.createStatement(subject, property, vf.createLiteral(117)))); - assertTrue(connector.containsStatement(subject, property, null, false, Collections.emptySet())); - verify(conn, never()).hasStatement(subject, property, null, false); - } - - @Test - public void containsReturnsTrueWhenCentralConnectorsContainsStatementAndLocalDoesNot() throws Exception { - final Resource subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - final RepositoryConnection conn = mock(RepositoryConnection.class); - when(conn.hasStatement(subject, property, null, false)).thenReturn(true); - when(centralMock.acquireConnection()).thenReturn(conn); - connector.begin(); - assertTrue(connector.containsStatement(subject, property, null, false, Collections.emptySet())); - } - - @Test - public void containsReturnsFalseWhenStatementsWasRemovedLocally() throws Exception { - final Resource subject = vf.createIRI(Generator.generateUri().toString()); - final IRI property = vf.createIRI(Generator.generateUri().toString()); - final RepositoryConnection conn = mock(RepositoryConnection.class); - when(centralMock.acquireConnection()).thenReturn(conn); - connector.begin(); - connector.removeStatements( - Collections.singletonList(vf.createStatement(subject, property, vf.createLiteral(117)))); - assertFalse(connector.containsStatement(subject, property, null, false, Collections.emptySet())); - } -} diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnectionTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnectionTest.java new file mode 100644 index 000000000..5b1e1117c --- /dev/null +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnectionTest.java @@ -0,0 +1,103 @@ +package cz.cvut.kbss.ontodriver.rdf4j.connector; + +import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; +import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.eclipse.rdf4j.model.vocabulary.RDFS; +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.RepositoryException; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.sail.inferencer.fc.SchemaCachingRDFSInferencer; +import org.eclipse.rdf4j.sail.memory.MemoryStore; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class StorageConnectionTest { + + private Repository repository; + + private StorageConnection sut; + + @AfterEach + void tearDown() throws Exception { + if (repository.isInitialized()) { + if (sut != null) { + sut.close(); + } + repository.shutDown(); + } + } + + @Test + void isInferredReturnsTrueWhenStatementIsInferredInSpecifiedContext() throws Exception { + this.repository = new SailRepository(new SchemaCachingRDFSInferencer(new MemoryStore())); + final StorageConnector connector = mock(StorageConnector.class); + when(connector.acquireConnection()).thenReturn(repository.getConnection()); + this.sut = new StorageConnection(connector, null); + + final ValueFactory vf = SimpleValueFactory.getInstance(); + final IRI childType = vf.createIRI(Generator.generateUri().toString()); + final IRI parentType = vf.createIRI(Generator.generateUri().toString()); + final IRI instance = vf.createIRI(Generator.generateUri().toString()); + final URI context = Generator.generateUri(); + try (final RepositoryConnection conn = repository.getConnection()) { + conn.begin(); + conn.add(childType, RDFS.SUBCLASSOF, parentType, vf.createIRI(context.toString())); + conn.add(instance, RDF.TYPE, childType, vf.createIRI(context.toString())); + conn.commit(); + } + + sut.begin(); + try { + assertFalse(sut.isInferred(vf.createStatement(instance, RDF.TYPE, parentType), Collections.singleton(vf.createIRI(Generator.generateUri() + .toString())))); + assertTrue(sut.isInferred(vf.createStatement(instance, RDF.TYPE, parentType), Collections.singleton(vf.createIRI(context.toString())))); + assertTrue(sut.isInferred(vf.createStatement(instance, RDF.TYPE, parentType), Collections.emptySet())); + } finally { + sut.rollback(); + } + } + + @Test + void beginUsesConfiguredTransactionIsolationLevel() throws Exception { + this.repository = new SailRepository(new MemoryStore()); + final ValueFactory vf = repository.getValueFactory(); + final IRI subject = repository.getValueFactory().createIRI(Generator.generateUri().toString()); + try (final RepositoryConnection conn = repository.getConnection()) { + conn.add(subject, RDFS.LABEL, vf.createLiteral("oldValue")); + } + final StorageConnector connector = mock(StorageConnector.class); + doAnswer(inv -> repository.getConnection()).when(connector).acquireConnection(); + this.sut = new StorageConnection(connector, IsolationLevels.SERIALIZABLE); + final StorageConnection sut2 = new StorageConnection(connector, IsolationLevels.SERIALIZABLE); + + sut.begin(); + sut2.begin(); + sut.removeStatements(sut.findStatements(subject, RDFS.LABEL, null, false)); + sut.addStatements(List.of(vf.createStatement(subject, RDFS.LABEL, vf.createLiteral("newValue1")))); + sut2.removeStatements(sut2.findStatements(subject, RDFS.LABEL, null, false)); + sut2.addStatements(List.of(vf.createStatement(subject, RDFS.LABEL, repository.getValueFactory() + .createLiteral("newValue2")))); + sut.commit(); + final Rdf4jDriverException ex = assertThrows(Rdf4jDriverException.class, sut2::commit); + sut2.close(); + assertInstanceOf(RepositoryException.class, ex.getCause()); + } +} diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnectorTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnectorTest.java index 2170fd719..b9dbda83d 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnectorTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnectorTest.java @@ -19,17 +19,10 @@ import cz.cvut.kbss.ontodriver.config.DriverConfiguration; import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; -import cz.cvut.kbss.ontodriver.rdf4j.connector.init.RepositoryConnectorInitializer; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import cz.cvut.kbss.ontodriver.rdf4j.environment.TestUtils; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.exception.RepositoryCreationException; -import org.eclipse.rdf4j.model.IRI; -import org.eclipse.rdf4j.model.Statement; -import org.eclipse.rdf4j.model.ValueFactory; -import org.eclipse.rdf4j.model.impl.SimpleValueFactory; -import org.eclipse.rdf4j.model.vocabulary.RDF; -import org.eclipse.rdf4j.model.vocabulary.RDFS; import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.RepositoryException; @@ -54,14 +47,13 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collection; -import java.util.Collections; import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -75,12 +67,12 @@ class StorageConnectorTest { private File repositoryFolder; - private StorageConnector connector; + private StorageConnector sut; @AfterEach void tearDown() throws Exception { - if (connector != null && connector.isOpen()) { - connector.close(); + if (sut != null && sut.isOpen()) { + sut.close(); } if (repositoryFolder != null && repositoryFolder.exists()) { deleteRecursive(repositoryFolder); @@ -106,7 +98,7 @@ void nonExistingParentFoldersAreCreatedWhenStorageIsInitialized() throws Excepti final File parentDir = new File(projectRootPath + File.separator + "internal"); assertFalse(parentDir.exists()); this.repositoryFolder = parentDir; - connector =createConnector(TestUtils.createDriverConfig(fileUri.toString())); + this.sut = createSut(TestUtils.createDriverConfig(fileUri.toString())); assertTrue(parentDir.exists()); final File repositoryDir = new File(fileUri); assertTrue(repositoryDir.exists()); @@ -118,10 +110,10 @@ private String getProjectRootPath() { return projectRootPath; } - private static StorageConnector createConnector(DriverConfiguration config) throws Rdf4jDriverException { - final RepositoryConnectorInitializer initializer = new RepositoryConnectorInitializer(config); - initializer.initializeRepository(); - return new StorageConnector(initializer); + private static StorageConnector createSut(DriverConfiguration config) throws Rdf4jDriverException { + final StorageConnector connector = new StorageConnector(config); + connector.initializeRepository(); + return connector; } @Test @@ -132,7 +124,7 @@ void invalidLocalRepositoryPathThrowsRepositoryCreationException() { assertFalse(parentDir.exists()); this.repositoryFolder = parentDir; assertThrows(RepositoryCreationException.class, - () -> createConnector(TestUtils.createDriverConfig(invalidUri.toString()))); + () -> createSut(TestUtils.createDriverConfig(invalidUri.toString()))); } @Test @@ -148,21 +140,21 @@ void connectorIsAbleToConnectToAlreadyInitializedLocalNativeStorage() throws Exc repoManager.addRepositoryConfig(config); repoManager.getRepository(repoId); - final StorageConnector connector = createConnector(TestUtils.createDriverConfig(repoUri.toString())); - assertTrue(connector.isOpen()); - connector.close(); + this.sut = createSut(TestUtils.createDriverConfig(repoUri.toString())); + assertTrue(sut.isOpen()); + sut.close(); } @Test void unwrapReturnsItselfWhenClassMatches() throws Exception { createInMemoryConnector(); - assertSame(connector, connector.unwrap(StorageConnector.class)); + assertSame(sut, sut.unwrap(StorageConnector.class)); } @Test void unwrapReturnsUnderlyingRepository() throws Exception { createInMemoryConnector(); - final Repository repo = connector.unwrap(Repository.class); + final Repository repo = sut.unwrap(Repository.class); assertNotNull(repo); assertTrue(repo.isInitialized()); } @@ -170,13 +162,13 @@ void unwrapReturnsUnderlyingRepository() throws Exception { private void createInMemoryConnector() throws Rdf4jDriverException { final DriverConfiguration conf = TestUtils.createDriverConfig("test"); conf.setProperty(Rdf4jConfigParam.USE_VOLATILE_STORAGE, Boolean.TRUE.toString()); - this.connector = createConnector(conf); + this.sut = createSut(conf); } @Test void unwrapOfUnsupportedClassThrowsException() throws Exception { createInMemoryConnector(); - assertThrows(Rdf4jDriverException.class, () -> connector.unwrap(Boolean.class)); + assertThrows(Rdf4jDriverException.class, () -> sut.unwrap(Boolean.class)); } @Test @@ -184,63 +176,49 @@ void setRepositoryReplacesOriginalInMemoryRepositoryWithSpecifiedOne() throws Ex createInMemoryConnector(); final Repository newRepository = new SailRepository(new MemoryStore()); Generator.initTestData(newRepository); - connector.setRepository(newRepository); - final Collection content = connector.findStatements(null, null, null, false); - try (RepositoryConnection conn = newRepository.getConnection()) { - assertEquals(conn.getStatements(null, null, null, false).stream().collect(Collectors.toList()), content); + sut.setRepository(newRepository); + try (RepositoryConnection expectedConn = newRepository.getConnection()) { + try (RepositoryConnection actualConn = sut.acquireConnection()) { + assertEquals(expectedConn.getStatements(null, null, null, false).stream().toList(), + actualConn.getStatements(null, null, null, false).stream().toList()); + } } } @Test void setRepositoryThrowsUnsupportedOperationWhenOriginalRepositoryIsNotInMemory() throws Exception { this.repositoryFolder = Files.createTempDirectory("rdf4j-storage-connector-test").toFile(); - connector = createConnector(TestUtils.createDriverConfig(Paths.get(repositoryFolder + this.sut = createSut(TestUtils.createDriverConfig(Paths.get(repositoryFolder .getAbsolutePath() + File.separator + "repositories" + File.separator + "test").toUri().toString())); final Repository newRepository = new SailRepository(new MemoryStore()); try { final UnsupportedOperationException result = - assertThrows(UnsupportedOperationException.class, () -> connector.setRepository(newRepository)); + assertThrows(UnsupportedOperationException.class, () -> sut.setRepository(newRepository)); assertEquals("Cannot replace repository which is not in-memory.", result.getMessage()); } finally { newRepository.shutDown(); } } - @Test - void setRepositoryThrowsIllegalStateExceptionWhenConnectorIsInTransaction() throws Exception { - createInMemoryConnector(); - connector.begin(); - final Repository newRepository = new SailRepository(new MemoryStore()); - - try { - final IllegalStateException result = - assertThrows(IllegalStateException.class, () -> connector.setRepository(newRepository)); - assertEquals("Cannot replace repository in transaction.", result.getMessage()); - } finally { - newRepository.shutDown(); - connector.rollback(); - } - } - @Test void initializationLoadsRepositoryConfigurationFromFileOnClasspathAndCreatesRepo() throws Exception { final DriverConfiguration conf = TestUtils.createDriverConfig("test"); conf.setProperty(Rdf4jConfigParam.REPOSITORY_CONFIG, "classpath:repo-configs/memory-rdfs.ttl"); - this.connector = createConnector(conf); - final Repository repo = connector.unwrap(Repository.class); - assertTrue(repo instanceof SailRepository); - assertTrue(((SailRepository) repo).getSail() instanceof SchemaCachingRDFSInferencer); + this.sut = createSut(conf); + final Repository repo = sut.unwrap(Repository.class); + assertInstanceOf(SailRepository.class, repo); + assertInstanceOf(SchemaCachingRDFSInferencer.class, ((SailRepository) repo).getSail()); } @Test void initializationSupportsLegacyRepositoryConfigurationVocabulary() throws Exception { final DriverConfiguration conf = TestUtils.createDriverConfig("test"); conf.setProperty(Rdf4jConfigParam.REPOSITORY_CONFIG, "classpath:repo-configs/memory-rdfs-legacy.ttl"); - this.connector = createConnector(conf); - final Repository repo = connector.unwrap(Repository.class); - assertTrue(repo instanceof SailRepository); - assertTrue(((SailRepository) repo).getSail() instanceof SchemaCachingRDFSInferencer); + this.sut = createSut(conf); + final Repository repo = sut.unwrap(Repository.class); + assertInstanceOf(SailRepository.class, repo); + assertInstanceOf(SchemaCachingRDFSInferencer.class, ((SailRepository) repo).getSail()); } @Test @@ -255,10 +233,10 @@ void initializationLoadsRepositoryConfigurationFromFileWithAbsolutePathAndCreate file.toFile().deleteOnExit(); final DriverConfiguration conf = TestUtils.createDriverConfig("test"); conf.setProperty(Rdf4jConfigParam.REPOSITORY_CONFIG, file.toString()); - this.connector = createConnector(conf); - final Repository repo = connector.unwrap(Repository.class); - assertTrue(repo instanceof SailRepository); - assertTrue(((SailRepository) repo).getSail() instanceof SchemaCachingRDFSInferencer); + this.sut = createSut(conf); + final Repository repo = sut.unwrap(Repository.class); + assertInstanceOf(SailRepository.class, repo); + assertInstanceOf(SchemaCachingRDFSInferencer.class, ((SailRepository) repo).getSail()); } @Test @@ -266,7 +244,7 @@ void initializationThrowsRepositoryCreationExceptionWhenRepositoryConfigurationF final DriverConfiguration conf = TestUtils.createDriverConfig("test"); conf.setProperty(Rdf4jConfigParam.REPOSITORY_CONFIG, "classpath:repo-configs/memory-rdfs-unknown.ttl"); final RepositoryCreationException result = - assertThrows(RepositoryCreationException.class, () -> createConnector(conf)); + assertThrows(RepositoryCreationException.class, () -> createSut(conf)); assertThat(result.getMessage(), containsString("repo-configs/memory-rdfs-unknown.ttl")); } @@ -275,7 +253,7 @@ void initializationThrowsRepositoryCreationExceptionWhenRepositoryConfigurationF final DriverConfiguration conf = TestUtils.createDriverConfig("test"); conf.setProperty(Rdf4jConfigParam.REPOSITORY_CONFIG, "/tmp/memory-rdfs-unknown.ttl"); final RepositoryCreationException result = - assertThrows(RepositoryCreationException.class, () -> createConnector(conf)); + assertThrows(RepositoryCreationException.class, () -> createSut(conf)); assertThat(result.getMessage(), containsString("/tmp/memory-rdfs-unknown.ttl")); } @@ -287,10 +265,10 @@ void initializationLoadsRepositoryConfigurationFromFileAndCreatesNativeRepo() th .toString(), File.separator + "repositories" + File.separator + "native-lucene").toUri().toString(); final DriverConfiguration conf = TestUtils.createDriverConfig(physicalUri); conf.setProperty(Rdf4jConfigParam.REPOSITORY_CONFIG, "classpath:repo-configs/native-lucene.ttl"); - this.connector = createConnector(conf); - final Repository repo = connector.unwrap(Repository.class); - assertTrue(repo instanceof SailRepository); - assertTrue(((SailRepository) repo).getSail() instanceof LuceneSail); + this.sut = createSut(conf); + final Repository repo = sut.unwrap(Repository.class); + assertInstanceOf(SailRepository.class, repo); + assertInstanceOf(LuceneSail.class, ((SailRepository) repo).getSail()); final File repoDir = new File(URI.create(physicalUri)); assertTrue(repoDir.exists()); } @@ -300,7 +278,7 @@ void initializationThrowsRdf4jDriverExceptionWhenReconnectAttemptsIsNotANumber() final DriverConfiguration conf = TestUtils.createDriverConfig("test"); conf.setProperty(Rdf4jConfigParam.RECONNECT_ATTEMPTS, "not-a-number"); conf.setProperty(Rdf4jConfigParam.USE_VOLATILE_STORAGE, Boolean.TRUE.toString()); - assertThrows(Rdf4jDriverException.class, () -> createConnector(conf)); + assertThrows(Rdf4jDriverException.class, () -> createSut(conf)); } @Test @@ -308,7 +286,7 @@ void initializationThrowsRdf4jDriverExceptionWhenReconnectAttemptsIsNegative() { final DriverConfiguration conf = TestUtils.createDriverConfig("test"); conf.setProperty(Rdf4jConfigParam.RECONNECT_ATTEMPTS, "-1"); conf.setProperty(Rdf4jConfigParam.USE_VOLATILE_STORAGE, Boolean.TRUE.toString()); - assertThrows(Rdf4jDriverException.class, () -> createConnector(conf)); + assertThrows(Rdf4jDriverException.class, () -> createSut(conf)); } @Test @@ -317,44 +295,16 @@ void getConnectionRetriesOnErrorConfiguredNumberOfTimes() throws Exception { final DriverConfiguration conf = TestUtils.createDriverConfig("test"); conf.setProperty(Rdf4jConfigParam.USE_VOLATILE_STORAGE, Boolean.TRUE.toString()); conf.setProperty(Rdf4jConfigParam.RECONNECT_ATTEMPTS, Integer.toString(attempts)); - this.connector = createConnector(conf); + this.sut = createSut(conf); final Repository repoMock = mock(Repository.class); final Field repoField = StorageConnector.class.getDeclaredField("repository"); repoField.setAccessible(true); - ((Repository) repoField.get(connector)).shutDown(); - repoField.set(connector, repoMock); + ((Repository) repoField.get(sut)).shutDown(); + repoField.set(sut, repoMock); when(repoMock.getConnection()).thenThrow(RepositoryException.class); when(repoMock.isInitialized()).thenReturn(true); - assertThrows(Rdf4jDriverException.class, () -> connector.acquireConnection()); + assertThrows(Rdf4jDriverException.class, () -> sut.acquireConnection()); verify(repoMock, times(attempts)).getConnection(); } - - @Test - void isInferredReturnsTrueWhenStatementIsInferredInSpecifiedContext() throws Exception { - final DriverConfiguration conf = TestUtils.createDriverConfig("test"); - conf.setProperty(Rdf4jConfigParam.USE_VOLATILE_STORAGE, Boolean.TRUE.toString()); - conf.setProperty(Rdf4jConfigParam.USE_INFERENCE, Boolean.TRUE.toString()); - this.connector = createConnector(conf); - final ValueFactory vf = SimpleValueFactory.getInstance(); - final IRI childType = vf.createIRI(Generator.generateUri().toString()); - final IRI parentType = vf.createIRI(Generator.generateUri().toString()); - final IRI instance = vf.createIRI(Generator.generateUri().toString()); - final URI context = Generator.generateUri(); - try (final RepositoryConnection conn = connector.unwrap(Repository.class).getConnection()) { - conn.begin(); - conn.add(childType, RDFS.SUBCLASSOF, parentType, vf.createIRI(context.toString())); - conn.add(instance, RDF.TYPE, childType, vf.createIRI(context.toString())); - conn.commit(); - } - - connector.begin(); - try { - assertFalse(connector.isInferred(vf.createStatement(instance, RDF.TYPE, parentType), Collections.singleton(vf.createIRI(Generator.generateUri().toString())))); - assertTrue(connector.isInferred(vf.createStatement(instance, RDF.TYPE, parentType), Collections.singleton(vf.createIRI(context.toString())))); - assertTrue(connector.isInferred(vf.createStatement(instance, RDF.TYPE, parentType), Collections.emptySet())); - } finally { - connector.rollback(); - } - } } diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/FactoryOfFactoriesTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/FactoryOfFactoriesTest.java index f4b7ff20c..2a05516b8 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/FactoryOfFactoriesTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/FactoryOfFactoriesTest.java @@ -1,60 +1,32 @@ -/* - * JOPA - * Copyright (C) 2023 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.ontodriver.rdf4j.connector.init; -import org.eclipse.rdf4j.model.ValueFactory; -import org.eclipse.rdf4j.model.impl.SimpleValueFactory; -import org.eclipse.rdf4j.query.BooleanQuery; -import org.eclipse.rdf4j.repository.Repository; -import org.eclipse.rdf4j.repository.RepositoryConnection; +import cz.cvut.kbss.ontodriver.config.DriverConfiguration; +import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; +import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectionFactoryConfig; +import cz.cvut.kbss.ontodriver.rdf4j.environment.TestUtils; +import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; -@ExtendWith(MockitoExtension.class) class FactoryOfFactoriesTest { - @Mock - private Repository repository; - - @Mock - - private RepositoryConnection connection; - @Test - void isRepositoryGraphDBChecksForPresenceOfInternalGraphDBEntityIds() throws Exception { - when(repository.getConnection()).thenReturn(connection); - final ValueFactory vf = SimpleValueFactory.getInstance(); - when(connection.getValueFactory()).thenReturn(vf); - final BooleanQuery query = mock(BooleanQuery.class); - when(connection.prepareBooleanQuery(anyString())).thenReturn(query); - when(query.evaluate()).thenReturn(true); - - assertTrue(FactoryOfFactories.isRepositoryGraphDB(repository)); - verify(query).setBinding(anyString(), eq(vf.createIRI(FactoryOfFactories.GRAPHDB_INTERNAL_ID_PROPERTY))); + void initializationResolvesThatUnderlyingRepoIsNotGraphDBWhenItDoesNotContainGraphDBInternalIdStatements() throws Exception { + final DriverConfiguration conf = TestUtils.createDriverConfig("test"); + conf.setProperty(Rdf4jConfigParam.USE_VOLATILE_STORAGE, Boolean.TRUE.toString()); + final FactoryOfFactories sut = new FactoryOfFactories(conf); + assertFalse(sut.resolveFactoryConfig().isGraphDB()); } + @Test + void resolveFactoryConfigResolvesTransactionIsolationLevelFromConfiguration() throws Exception { + final DriverConfiguration conf = TestUtils.createDriverConfig("test"); + conf.setProperty(Rdf4jConfigParam.USE_VOLATILE_STORAGE, Boolean.TRUE.toString()); + conf.setProperty(Rdf4jConfigParam.TRANSACTION_ISOLATION_LEVEL, IsolationLevels.SERIALIZABLE.toString()); + final FactoryOfFactories sut = new FactoryOfFactories(conf); + final ConnectionFactoryConfig result = sut.resolveFactoryConfig(); + assertEquals(IsolationLevels.SERIALIZABLE, result.txIsolationLevel()); + } } diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/environment/TestRepositoryProvider.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/environment/TestRepositoryProvider.java index 555b328a5..1284eee78 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/environment/TestRepositoryProvider.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/environment/TestRepositoryProvider.java @@ -23,32 +23,31 @@ import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.rdf4j.Rdf4jDataSource; import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; -import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectorFactory; -import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectorFactoryImpl; +import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectionFactory; +import cz.cvut.kbss.ontodriver.rdf4j.connector.ConnectionFactoryImpl; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.connector.StorageConnector; -import cz.cvut.kbss.ontodriver.rdf4j.connector.init.RepositoryConnectorInitializer; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import java.net.URI; public class TestRepositoryProvider { - private ConnectorFactory factory; + private ConnectionFactory factory; public TestRepositoryProvider() { } - public Connector createConnector(boolean useInference) throws Rdf4jDriverException { + public RepoConnection createConnector(boolean useInference) throws Rdf4jDriverException { final DriverConfiguration configuration = new DriverConfiguration(storageProperties()); configuration.setProperty(Rdf4jConfigParam.USE_VOLATILE_STORAGE, Boolean.TRUE.toString()); configuration.setProperty(Rdf4jConfigParam.USE_INFERENCE, Boolean.toString(useInference)); configuration.setProperty(DriverConfigParam.USE_TRANSACTIONAL_ONTOLOGY, Boolean.TRUE.toString()); - final RepositoryConnectorInitializer connectorInitializer = new RepositoryConnectorInitializer(configuration); + final StorageConnector connectorInitializer = new StorageConnector(configuration); connectorInitializer.initializeRepository(); - this.factory = new ConnectorFactoryImpl(new StorageConnector(connectorInitializer)); - return factory.createStorageConnector(); + this.factory = new ConnectionFactoryImpl(connectorInitializer); + return factory.createStorageConnection(); } public void close() throws OntoDriverException { diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ListHandlerWithStorageTestBase.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ListHandlerWithStorageTestBase.java index 7e1cbb76e..66625fd3f 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ListHandlerWithStorageTestBase.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ListHandlerWithStorageTestBase.java @@ -19,7 +19,7 @@ import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import cz.cvut.kbss.ontodriver.rdf4j.environment.TestRepositoryProvider; import org.junit.jupiter.api.AfterEach; @@ -39,7 +39,7 @@ abstract class ListHandlerWithStorageTestBase { final TestRepositoryProvider repositoryProvider = new TestRepositoryProvider(); - protected Connector connector; + protected RepoConnection connector; @AfterEach public void tearDown() throws Exception { diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListHandlerTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListHandlerTest.java index d59be5f6c..4f15947ea 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListHandlerTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListHandlerTest.java @@ -26,7 +26,7 @@ import cz.cvut.kbss.ontodriver.model.LangString; import cz.cvut.kbss.ontodriver.model.MultilingualString; import cz.cvut.kbss.ontodriver.model.NamedResource; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import cz.cvut.kbss.ontodriver.rdf4j.environment.Vocabulary; import cz.cvut.kbss.ontodriver.rdf4j.util.Rdf4jUtils; @@ -37,6 +37,7 @@ import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -50,9 +51,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -60,9 +63,12 @@ import static cz.cvut.kbss.ontodriver.rdf4j.list.ListHandlerTestHelper.NEXT_NODE_PROPERTY; import static cz.cvut.kbss.ontodriver.rdf4j.list.ListHandlerTestHelper.OWNER; import static cz.cvut.kbss.ontodriver.rdf4j.list.ListHandlerTestHelper.generateList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; @@ -94,7 +100,7 @@ public class ReferencedListHandlerTest { URI.create(ListHandlerTestHelper.NODE_CONTENT_PROPERTY), false); @Mock - private Connector connector; + private RepoConnection connector; private ReferencedListDescriptor listDescriptor; @@ -266,7 +272,7 @@ public void doesNothingWhenNoValuesArePassedToPersist() throws Exception { @Test public void clearsListOnUpdateWhenDescriptorHasNoValues() throws Exception { - final ReferencedListValueDescriptor descriptor = initValues(0); + final ReferencedListValueDescriptor descriptor = createValueDescriptor(Assertion.AssertionType.OBJECT_PROPERTY, false); // old list final List refList = generateList(); final List oldList = initStatementsForList(initListNodes(refList), refList); @@ -279,20 +285,21 @@ public void clearsListOnUpdateWhenDescriptorHasNoValues() throws Exception { } private static ReferencedListValueDescriptor initValues(int count) { - final ReferencedListValueDescriptor desc = createValueDescriptor(Assertion.AssertionType.OBJECT_PROPERTY); + final ReferencedListValueDescriptor desc = createValueDescriptor(Assertion.AssertionType.OBJECT_PROPERTY, false); for (int i = 0; i < count; i++) { desc.addValue(NamedResource.create(Vocabulary.INDIVIDUAL_IRI_BASE + i)); } return desc; } - private static ReferencedListValueDescriptor createValueDescriptor(Assertion.AssertionType assertionType) { + private static ReferencedListValueDescriptor createValueDescriptor(Assertion.AssertionType assertionType, + boolean nilTerminal) { final Assertion contentAssertion = assertionType == Assertion.AssertionType.OBJECT_PROPERTY ? Assertion.createObjectPropertyAssertion( URI.create(ListHandlerTestHelper.NODE_CONTENT_PROPERTY), false) : Assertion.createDataPropertyAssertion(URI.create(ListHandlerTestHelper.NODE_CONTENT_PROPERTY), false); return new ReferencedListValueDescriptor<>(OWNER, Assertion.createObjectPropertyAssertion(URI.create(LIST_PROPERTY), false), Assertion.createObjectPropertyAssertion(URI.create(NEXT_NODE_PROPERTY), - false), contentAssertion); + false), contentAssertion, nilTerminal); } @Test @@ -343,7 +350,7 @@ public void updateListAddsNewValuesToTheEnd() throws Exception { @Test void persistListSupportsSavingDataPropertyValuesAsListElements() throws Exception { - final ReferencedListValueDescriptor desc = createValueDescriptor(Assertion.AssertionType.DATA_PROPERTY); + final ReferencedListValueDescriptor desc = createValueDescriptor(Assertion.AssertionType.DATA_PROPERTY, false); IntStream.range(0, 5).mapToObj(i -> Generator.randomInt()).forEach(desc::addValue); sut.persistList(desc); @@ -387,7 +394,7 @@ void persistListSavesMultilingualStringTranslationsAsContentOfSingleNode() throw new MultilingualString(Map.of("en", "one", "cs", "jedna")), new MultilingualString(Map.of("en", "two", "cs", "dva")) ); - final ReferencedListValueDescriptor desc = createValueDescriptor(Assertion.AssertionType.DATA_PROPERTY); + final ReferencedListValueDescriptor desc = createValueDescriptor(Assertion.AssertionType.DATA_PROPERTY, false); refList.forEach(desc::addValue); sut.persistList(desc); @@ -415,4 +422,115 @@ void persistListSavesMultilingualStringTranslationsAsContentOfSingleNode() throw i++; } } + + @Test + void persistListAppendsRdfNilAfterLastNodeWhenDescriptorIsConfiguredToTerminateListWithIt() throws Exception { + final List values = generateList(); + final ReferencedListValueDescriptor desc = createValueDescriptor(Assertion.AssertionType.OBJECT_PROPERTY, true); + values.forEach(desc::addValue); + + sut.persistList(desc); + final ArgumentCaptor captor = ArgumentCaptor.forClass(Collection.class); + verify(connector).addStatements(captor.capture()); + final Collection stmts = captor.getValue(); + final Iterator it = stmts.iterator(); + Statement statement = null; + while (it.hasNext()) { + statement = it.next(); + } + assertNotNull(statement); + assertEquals(vf.createIRI(NEXT_NODE_PROPERTY), statement.getPredicate()); + assertEquals(RDF.NIL, statement.getObject()); + } + + @Test + void loadListLoadsNilTerminatedList() throws Exception { + final List refList = generateList(); + final List listNodes = initListNodes(refList); + initStatementsForList(listNodes, refList); + final IRI lastNodeIri = vf.createIRI(listNodes.get(listNodes.size() - 1).toString()); + when(connector.findStatements(lastNodeIri, vf.createIRI(NEXT_NODE_PROPERTY), null, false, Collections.emptySet())) + .thenReturn(Set.of(vf.createStatement(lastNodeIri, vf.createIRI(NEXT_NODE_PROPERTY), RDF.NIL))); + final Collection> res = sut.loadList(listDescriptor); + assertEquals(refList.size(), res.size()); + for (Axiom a : res) { + assertInstanceOf(NamedResource.class, a.getValue().getValue()); + assertTrue(refList.contains((NamedResource) a.getValue().getValue())); + } + } + + @Test + void clearListRemovesListNodesIncludingNilTerminal() throws Exception { + final ReferencedListValueDescriptor descriptor = createValueDescriptor(Assertion.AssertionType.OBJECT_PROPERTY, true); + // old list + final List refList = generateList(); + final List oldListNodes = initListNodes(refList); + final List oldList = initStatementsForList(oldListNodes, refList); + final IRI lastNodeIri = vf.createIRI(oldListNodes.get(oldListNodes.size() - 1).toString()); + final Statement nilTerminal = vf.createStatement(lastNodeIri, vf.createIRI(NEXT_NODE_PROPERTY), RDF.NIL); + when(connector.findStatements(lastNodeIri, vf.createIRI(NEXT_NODE_PROPERTY), null, false, Collections.emptySet())) + .thenReturn(Set.of(nilTerminal)); + + sut.updateList(descriptor); + verify(connector).removeStatements(anyCollection()); + verify(connector, never()).addStatements(anyCollection()); + assertEquals(oldList.size() + 1, removed.size()); + assertTrue(removed.containsAll(oldList)); + assertThat(removed, hasItem(nilTerminal)); + } + + @Test + void updateListRemovesNilTerminalFromPreviouslyLastNodeAndAppendsItToNewLastNode() throws Exception { + final ReferencedListValueDescriptor descriptor = createValueDescriptor(Assertion.AssertionType.OBJECT_PROPERTY, true); + final List addedItems = IntStream.range(0, 5) + .mapToObj(i -> NamedResource.create(Vocabulary.INDIVIDUAL_IRI_BASE + i)) + .toList(); + final List oldList = generateList(); + final List oldNodes = initListNodes(oldList); + initStatementsForList(oldNodes, oldList); + final IRI lastNodeIri = vf.createIRI(oldNodes.get(oldNodes.size() - 1).toString()); + final Statement nilTerminal = vf.createStatement(lastNodeIri, vf.createIRI(NEXT_NODE_PROPERTY), RDF.NIL); + when(connector.findStatements(lastNodeIri, vf.createIRI(NEXT_NODE_PROPERTY), null, false, Collections.emptySet())) + .thenReturn(Set.of(nilTerminal)); + // The original items + for (NamedResource item : oldList) { + descriptor.addValue(item); + } + // Now add the new ones + for (NamedResource r : addedItems) { + descriptor.addValue(r); + } + + sut.updateList(descriptor); + verify(connector).removeStatements(Set.of(nilTerminal)); + verify(connector, atLeast(1)).addStatements(anyCollection()); + // Added nodes with values + terminal + assertEquals(addedItems.size() * 2 + 1, added.size()); + for (Statement stmt : added) { + if (stmt.getPredicate().equals(nodeContentProperty)) { + final URI u = URI.create(stmt.getObject().stringValue()); + assertTrue(addedItems.contains(NamedResource.create(u))); + } + } + assertTrue(added.stream().anyMatch(s -> s.getObject().equals(RDF.NIL))); + } + + @Test + void updateListRemovesNilTerminalFromLastNodeWhenItIsRemovedAndAddsItToNewLastNode() throws Exception { + final ReferencedListValueDescriptor descriptor = createValueDescriptor(Assertion.AssertionType.OBJECT_PROPERTY, true); + final List oldList = generateList(); + final List oldNodes = initListNodes(oldList); + initStatementsForList(oldNodes, oldList); + final IRI lastNodeIri = vf.createIRI(oldNodes.get(oldNodes.size() - 1).toString()); + final Statement nilTerminal = vf.createStatement(lastNodeIri, vf.createIRI(NEXT_NODE_PROPERTY), RDF.NIL); + when(connector.findStatements(lastNodeIri, vf.createIRI(NEXT_NODE_PROPERTY), null, false, Collections.emptySet())) + .thenReturn(Set.of(nilTerminal)); + // The original items + for (NamedResource item : oldList.subList(0, oldList.size() / 2)) { + descriptor.addValue(item); + } + sut.updateList(descriptor); + assertThat(removed, hasItem(nilTerminal)); + assertTrue(added.stream().anyMatch(s -> s.getObject().equals(RDF.NIL))); + } } diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListIteratorTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListIteratorTest.java index e928001f4..7bf0bdfbe 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListIteratorTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/ReferencedListIteratorTest.java @@ -7,7 +7,7 @@ import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.MultilingualString; import cz.cvut.kbss.ontodriver.model.NamedResource; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Statement; @@ -37,7 +37,7 @@ class ReferencedListIteratorTest { private static final ValueFactory VF = SimpleValueFactory.getInstance(); @Mock - private Connector connector; + private RepoConnection connector; @Test void nextNodeThrowsIntegrityConstraintViolatedExceptionWhenNodeHasMultipleContentValues() throws Exception { diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListHandlerTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListHandlerTest.java index 2593e9921..b1ae5e83e 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListHandlerTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/list/SimpleListHandlerTest.java @@ -24,7 +24,7 @@ import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.NamedResource; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.environment.Vocabulary; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; import cz.cvut.kbss.ontodriver.rdf4j.util.Rdf4jUtils; @@ -82,7 +82,7 @@ public class SimpleListHandlerTest { private static IRI nextNodeProperty = vf.createIRI(NEXT_NODE_PROPERTY); @Mock - private Connector connector; + private RepoConnection connector; private SimpleListDescriptor listDescriptor; diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoaderTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoaderTest.java index c7806b5f7..ff8247e0c 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoaderTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/loader/DefaultContextInferenceStatementLoaderTest.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.NamedResource; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import cz.cvut.kbss.ontodriver.rdf4j.util.AxiomBuilder; import org.eclipse.rdf4j.model.Statement; @@ -56,7 +56,7 @@ class DefaultContextInferenceStatementLoaderTest { private static final ValueFactory VF = SimpleValueFactory.getInstance(); @Mock - private Connector connector; + private RepoConnection connector; @Mock private AxiomBuilder axiomBuilder; diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoaderTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoaderTest.java index 5569645ec..200ad0c21 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoaderTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/loader/GraphDBStatementLoaderTest.java @@ -20,7 +20,7 @@ import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.NamedResource; -import cz.cvut.kbss.ontodriver.rdf4j.connector.Connector; +import cz.cvut.kbss.ontodriver.rdf4j.connector.RepoConnection; import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import cz.cvut.kbss.ontodriver.rdf4j.util.AxiomBuilder; import org.eclipse.rdf4j.model.IRI; @@ -59,7 +59,7 @@ class GraphDBStatementLoaderTest { private static final ValueFactory VF = SimpleValueFactory.getInstance(); @Mock - private Connector connector; + private RepoConnection connector; @Mock private AxiomBuilder axiomBuilder; diff --git a/pom.xml b/pom.xml index 4a51d33f5..7071bfa72 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 cz.cvut.kbss.jopa - 1.2.2 + 2.0.0 jopa-all pom JOPA @@ -34,23 +34,21 @@ UTF-8 - 11 - ${jdk.version} - ${jdk.version} + 17 + ${java.version} + ${java.version} 2.0.6 5.9.2 5.3.1 - 1.9.20 1.4.12 5.5.0 - 1.13.1 3.1.1 - 3.6.0 - 2.22.2 + 3.6.3 + 3.2.5 @@ -130,14 +128,10 @@ attach-javadocs - jar + aggregate-jar - - -Xdoclint:none - true - org.apache.maven.plugins @@ -170,6 +164,27 @@ + + kbss + + + kbss + sftp://kbss.felk.cvut.cz/var/www/m2repo + + + + + ossrh + + true + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + @@ -189,14 +204,14 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.12.1 org.jacoco jacoco-maven-plugin - 0.8.8 + 0.8.11 @@ -254,58 +269,6 @@ ${maven.surefire.plugin.version} - - - - - dev.aspectj - aspectj-maven-plugin - ${maven.aspectj.plugin.version} - - ${jdk.version} - ${jdk.version} - ${jdk.version} - - - cz.cvut.kbss.jopa - jopa-impl - - - - - - org.aspectj - aspectjtools - ${org.aspectj.version} - - - org.aspectj - aspectjrt - ${org.aspectj.version} - - - - - compile - process-classes - - compile - - - - - - test-compile - process-test-classes - - - test-compile - - - - - - @@ -349,10 +312,6 @@ - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - ossrh https://oss.sonatype.org/service/local/staging/deploy/maven2/