diff --git a/CHANGELOG.md b/CHANGELOG.md index 963da1365..8544ee402 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # JOPA - Change Log +## 2.0.1 - 2024-06-17 +- Proper implementation of `EntityManager.getReference` after 2.0.0 rewrite (Enhancement #233). +- Log JOPA version and build date on persistence unit startup (Enhancement #243). +- Prevent `AssertionError` on `EntityManager.flush` calls (Bug #240). +- Fix incorrect SOQL to SPARQL translation when traversing reference and using identifier (Bug #234). +- Fix issues with interaction of lazy loading with cascading (Bug #248). +- Dependency updates: RDF4J 4.3.12. + ## 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). diff --git a/datatype/pom.xml b/datatype/pom.xml index 4c6a3e404..1135ee16b 100644 --- a/datatype/pom.xml +++ b/datatype/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 2.0.0 + 2.0.1 ../pom.xml diff --git a/jopa-api/pom.xml b/jopa-api/pom.xml index 2712eccc8..3c5eb5c92 100644 --- a/jopa-api/pom.xml +++ b/jopa-api/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.0.0 + 2.0.1 ../pom.xml diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/EntityNotFoundException.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/EntityNotFoundException.java index a4829be6f..60f81abfe 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/EntityNotFoundException.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/exceptions/EntityNotFoundException.java @@ -20,9 +20,12 @@ import cz.cvut.kbss.jopa.model.EntityManager; /** - * Thrown when {@link EntityManager#refresh(Object)} is called and the object no longer exists in the database. + * Thrown by the persistence provider when an entity reference obtained by + * {@link EntityManager#getReference(Class, Object)} is accessed but the entity does not exist. Thrown when + * {@link EntityManager#refresh} is called and the object no longer exists in the database. *

- * The current transaction, if one is active and the persistence context has been joined to it, will be marked for rollback. + * The current transaction, if one is active and the persistence context has been joined to it, will be marked for + * rollback. */ public class EntityNotFoundException extends OWLPersistenceException { 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 4cfdeb141..a00d41d31 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 @@ -17,6 +17,7 @@ */ package cz.cvut.kbss.jopa.model.metamodel; +@FunctionalInterface public interface IdentifierVisitor { void visit(IRIIdentifier i); diff --git a/jopa-distribution/pom.xml b/jopa-distribution/pom.xml index 511994397..8cc8bfbd5 100644 --- a/jopa-distribution/pom.xml +++ b/jopa-distribution/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.0.0 + 2.0.1 ../pom.xml diff --git a/jopa-impl/pom.xml b/jopa-impl/pom.xml index df77d5467..c33d18f88 100644 --- a/jopa-impl/pom.xml +++ b/jopa-impl/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.0.0 + 2.0.1 ../pom.xml @@ -49,6 +49,12 @@ + + + src/main/resources + true + + org.antlr @@ -63,6 +69,11 @@ + + org.apache.maven.plugins + maven-resources-plugin + ${maven.resources.plugin.version} + org.apache.maven.plugins maven-surefire-plugin 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 f0be858e2..01a6b0099 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 @@ -88,9 +88,15 @@ protected void processElements(Enumeration urls, String scanPath) throws IO continue; } visited.add(elemUri); - LOG.trace("Processing classpath element {}", url); + LOG.trace("Processing classpath element {}", elemUri); if (isJar(elemUri)) { - processJarFile(createJarFile(url)); + final JarFile jarFile = createJarFile(url); + if (!elemUri.equals(jarFile.getName()) && visited.contains(jarFile.getName())) { + LOG.trace("Classpath element {} maps to a JAR file {} and it has already been processed.", elemUri, jarFile.getName()); + } else { + visited.add(jarFile.getName()); + processJarFile(jarFile); + } } else { processDirectory(new File(URI.create(elemUri).getPath()), scanPath); } 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 ceb0aa1ab..3b7f0bd5d 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 @@ -36,6 +36,7 @@ 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.JOPALazyUtils; import cz.cvut.kbss.jopa.utils.Wrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -227,7 +228,7 @@ protected void exploreCascaded(Attribute at, Object merged, Object toMerge private void mergeX(Attribute at, Object merged, Object toMerge, Descriptor descriptor) { Object attVal = EntityPropertiesUtils.getAttributeValue(at, toMerge); - if (attVal == null) { + if (attVal == null || JOPALazyUtils.isLazyLoadingProxy(attVal)) { return; } if (at.isCollection()) { @@ -261,7 +262,7 @@ public void remove(Object object) { registerProcessedInstance(object); // Intentional fall-through case REMOVED: - new SimpleOneLevelCascadeExplorer(this::remove).start(this, object, CascadeType.REMOVE); + new OneLevelRemoveCascadeExplorer(this::remove).start(this, object, CascadeType.REMOVE); break; default: throw new IllegalArgumentException("Entity " + object + " is not managed and cannot be removed."); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/JOPAPersistenceProvider.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/JOPAPersistenceProvider.java index e335afedc..b46874722 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/JOPAPersistenceProvider.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/JOPAPersistenceProvider.java @@ -17,12 +17,27 @@ */ package cz.cvut.kbss.jopa.model; -import java.util.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; public class JOPAPersistenceProvider implements PersistenceProvider, ProviderUtil { + private static final Logger LOG = LoggerFactory.getLogger(JOPAPersistenceProvider.class); + private static final Set EMFS = Collections.synchronizedSet(new HashSet<>()); + public JOPAPersistenceProvider() { + logVersionInfo(); + } + @Override public EntityManagerFactoryImpl createEntityManagerFactory(String emName, Map properties) { final EntityManagerFactoryImpl emf = new EntityManagerFactoryImpl(properties, this::emfClosed); @@ -30,6 +45,18 @@ public EntityManagerFactoryImpl createEntityManagerFactory(String emName, Map, Class> lazyLoadingProxyClasses = new ConcurrentHashMap<>(); + // Proxy classes for results of EntityManager.getReference + private final Map, Class> referenceProxyClasses = new ConcurrentHashMap<>(); private NamedQueryManager namedQueryManager; private ResultSetMappingManager resultSetMappingManager; @@ -151,10 +154,12 @@ public Set> getInferredClasses() { return Collections.unmodifiableSet(inferredClasses); } + @Override public NamedQueryManager getNamedQueryManager() { return namedQueryManager; } + @Override public ResultSetMappingManager getResultSetMappingManager() { return resultSetMappingManager; } @@ -222,7 +227,7 @@ public Set> getReferringTypes(Class 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 cls Class to get lazy loading proxy for, should be an entity class * @param Type to proxy * @return Lazy loading proxy class */ @@ -230,4 +235,16 @@ public Class getLazyLoadingProxy(Class cls) { assert isEntityType(cls); return (Class) lazyLoadingProxyClasses.computeIfAbsent(cls, c -> new LazyLoadingEntityProxyGenerator().generate(c)); } + + /** + * Gets a {@link cz.cvut.kbss.jopa.proxy.reference.EntityReferenceProxy} type for the specified class. + * + * @param cls Class to get reference proxy for, should be an entity class + * @param Type to proxy + * @return Entity proxy class + */ + public Class getEntityReferenceProxy(Class cls) { + assert isEntityType(cls); + return (Class) referenceProxyClasses.computeIfAbsent(cls, c -> new EntityReferenceProxyGenerator().generate(cls)); + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/OneLevelRemoveCascadeExplorer.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/OneLevelRemoveCascadeExplorer.java new file mode 100644 index 000000000..409fac4f9 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/OneLevelRemoveCascadeExplorer.java @@ -0,0 +1,37 @@ +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 cz.cvut.kbss.jopa.utils.JOPALazyUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.function.Consumer; + +public class OneLevelRemoveCascadeExplorer extends OneLevelCascadeExplorer { + + private final Consumer removeOperation; + + public OneLevelRemoveCascadeExplorer(Consumer removeOperation) { + this.removeOperation = removeOperation; + } + + @Override + protected void exploreCascaded(Attribute at, Object o) { + Object attVal = EntityPropertiesUtils.getAttributeValue(at, o); + if (attVal == null) { + return; + } + if (JOPALazyUtils.isLazyLoadingProxy(attVal)) { + attVal = ((LazyLoadingProxy) attVal).triggerLazyLoading(); + } + if (at.isCollection()) { + for (final Object ox2 : new HashSet<>((Collection) attVal)) { + removeOperation.accept(ox2); + } + } else { + removeOperation.accept(attVal); + } + } +} 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 4c5ca9da3..0e5edd1ff 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 @@ -28,8 +28,8 @@ import java.util.stream.Collectors; /** - * Instances of the type AbstractIdentifiableType represent entity or mapped superclass - * types which can be queried for attributes, subtypes and so on. + * Instances of the type AbstractIdentifiableType represent entity or mapped superclass types which can be queried for + * attributes, subtypes and so on. * * @param Entity type being represented by this instance */ @@ -70,8 +70,8 @@ void addDeclaredQueryAttribute(final String name, final AbstractQueryAttribute * 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() { 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 066fbe16b..b15b040c9 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 @@ -21,10 +21,9 @@ import cz.cvut.kbss.jopa.model.annotations.InheritanceType; /** - * Instances of the type IdentifiableEntityType represent entity - * types which can be saved to and read from storage. + * Instances of this type represent entity classes that can be saved to and read from storage. * - * @param Entity type being represented by this instance + * @param Entity class being represented by this instance */ public abstract class IdentifiableEntityType extends AbstractIdentifiableType implements EntityType { @@ -69,9 +68,10 @@ public IRI getIRI() { /** * Gets inheritance type of this entity type. *

- * If the entity type is a root if an inheritance hierarchy, the type can be defined using the {@link - * cz.cvut.kbss.jopa.model.annotations.Inheritance} annotation. If the entity is deeper in inheritance hierarchy, it - * is inherited from the supertype. Otherwise, it defaults to {@link cz.cvut.kbss.jopa.utils.Constants#DEFAULT_INHERITANCE_TYPE}. + * If the entity type is a root if an inheritance hierarchy, the type can be defined using the + * {@link cz.cvut.kbss.jopa.model.annotations.Inheritance} annotation. If the entity is deeper in inheritance + * hierarchy, it is inherited from the supertype. Otherwise, it defaults to + * {@link cz.cvut.kbss.jopa.utils.Constants#DEFAULT_INHERITANCE_TYPE}. * * @return Inheritance strategy for this entity type */ 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 index 521837230..2ef042c6d 100644 --- 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 @@ -84,6 +84,7 @@ public RDFCollectionAttributeBuilder converter(ConverterWrapper converter) return this; } + @Override public RDFCollectionAttribute build() { return new RDFCollectionAttribute<>(this); } 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 index d53b428ba..cad1aba0a 100644 --- 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 @@ -87,7 +87,11 @@ private void outputGeneratedClass(DynamicType.Unloaded typeDef) public static class SetterInterceptor { - public static void set(@This Manageable instance, @Origin Method setter) throws Exception { + private SetterInterceptor() { + throw new AssertionError(); + } + + public static void set(@This Manageable instance, @Origin Method setter) { final UnitOfWork pc = instance.getPersistenceContext(); if (pc == null || !pc.isInTransaction()) { return; 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 4c32d0bf1..442506e28 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 @@ -35,12 +35,6 @@ T loadEntity(LoadingParameters loadingParameters) { return loadInstance(loadingParameters, et); } - @Override - T loadReference(LoadingParameters loadingParameters) { - final IdentifiableEntityType et = metamodel.entity(loadingParameters.getEntityClass()); - return loadReferenceInstance(loadingParameters, et); - } - static DefaultInstanceLoaderBuilder builder() { return new DefaultInstanceLoaderBuilder(); } 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 4f818c24c..33ae36e65 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 @@ -203,6 +203,8 @@ private void populateQueryAttributes(T instance, EntityType et, LoadState if (queryAttribute.getFetchType() != FetchType.LAZY) { populateQueryAttribute(instance, queryAttribute, queryFactory, et); loadStateDescriptor.setLoaded(queryAttribute, LoadState.LOADED); + } else { + loadStateDescriptor.setLoaded(queryAttribute, LoadState.NOT_LOADED); } } } 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 588d8fce4..296617524 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 @@ -19,7 +19,6 @@ 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; @@ -27,6 +26,7 @@ import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; import cz.cvut.kbss.jopa.model.metamodel.PluralAttribute; import cz.cvut.kbss.jopa.oom.exception.EntityReconstructionException; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; @@ -36,7 +36,6 @@ import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.model.Axiom; -import cz.cvut.kbss.ontodriver.model.NamedResource; import java.net.URI; import java.util.Collection; @@ -83,19 +82,6 @@ abstract class EntityInstanceLoader { */ abstract T loadEntity(LoadingParameters loadingParameters); - /** - * Loads entity reference. - *

- * I.e., the object may contain only the identifier, other attributes could be loaded lazily. However, if a - * corresponding entity is already cached, it can be returned instead. - * - * @param loadingParameters Reference loading parameters. Note that cache bypassing and forced eager loading - * configuration is ignored - * @param Entity type - * @return Loaded entity reference, possibly {@code null} - */ - abstract T loadReference(LoadingParameters loadingParameters); - U loadInstance(LoadingParameters loadingParameters, IdentifiableEntityType et) { final URI identifier = loadingParameters.getIdentifier(); final Descriptor descriptor = loadingParameters.getDescriptor(); @@ -137,7 +123,9 @@ private LoadStateDescriptor getLoadStatDescriptor(Object instance, EntityType return LoadStateDescriptorFactory.createAllUnknown(instance, (EntityType) et); } - private void recursivelyProcessCachedEntityReferences(Object instance, EntityType et, Map visited, List>>> handlers) { + private void recursivelyProcessCachedEntityReferences(Object instance, EntityType et, + Map visited, + List>>> handlers) { if (visited.containsKey(instance)) { return; } @@ -161,20 +149,6 @@ private void recursivelyProcessCachedEntityReferences(Object instance, EntityTyp }); } - T loadReferenceInstance(LoadingParameters loadingParameters, IdentifiableEntityType et) { - final URI identifier = loadingParameters.getIdentifier(); - final Axiom typeAxiom = descriptorFactory.createForReferenceLoading(identifier, et); - try { - final boolean contains = - storageConnection.contains(typeAxiom, loadingParameters.getDescriptor().getContexts()); - return contains ? entityBuilder.createEntityInstance(identifier, et) : null; - } catch (OntoDriverException e) { - throw new StorageAccessException(e); - } catch (cz.cvut.kbss.jopa.exception.InstantiationException e) { - throw new EntityReconstructionException(e); - } - } - abstract static class EntityInstanceLoaderBuilder { private Connection storageConnection; private MetamodelImpl metamodel; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityReferenceFactory.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityReferenceFactory.java new file mode 100644 index 000000000..41007b1d1 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityReferenceFactory.java @@ -0,0 +1,50 @@ +package cz.cvut.kbss.jopa.oom; + +import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.model.MetamodelImpl; +import cz.cvut.kbss.jopa.proxy.reference.EntityReferenceProxy; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; +import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils; + +import java.lang.reflect.InvocationTargetException; + +class EntityReferenceFactory { + + private final MetamodelImpl metamodel; + + private final UnitOfWork uow; + + EntityReferenceFactory(MetamodelImpl metamodel, UnitOfWork uow) { + this.metamodel = metamodel; + this.uow = uow; + } + + /** + * Creates entity reference proxy with initialized identifier and associated persistence context. + * + * @param loadingParameters Parameters for creating the reference + * @param Type of the expected instance + * @return Entity reference proxy + */ + T createReferenceProxy(LoadingParameters loadingParameters) { + assert loadingParameters != null; + + final Class referenceProxyClass = metamodel.getEntityReferenceProxy(loadingParameters.getEntityClass()); + try { + final T reference = referenceProxyClass.getDeclaredConstructor().newInstance(); + assert reference instanceof EntityReferenceProxy; + final EntityReferenceProxy referenceProxy = (EntityReferenceProxy) reference; + referenceProxy.setIdentifier(loadingParameters.getIdentifier()); + referenceProxy.setType((Class) loadingParameters.getEntityClass()); + referenceProxy.setPersistenceContext(uow); + referenceProxy.setDescriptor(loadingParameters.getDescriptor()); + EntityPropertiesUtils.setIdentifier(loadingParameters.getIdentifier(), reference, metamodel.entity(loadingParameters.getEntityClass())); + return reference; + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { + // We do not expect this to happen, as we generated the proxy class + throw new OWLPersistenceException("Unable to instantiate entity reference proxy class " + referenceProxyClass, e); + } + } +} 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 index 70c205a4c..e9a799230 100644 --- 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 @@ -16,6 +16,10 @@ */ class ListDescriptorFactory { + private ListDescriptorFactory() { + throw new AssertionError(); + } + static SimpleListDescriptor createSimpleListDescriptor(NamedResource owner, ListAttribute attribute) { final boolean inferred = attribute.isInferred(); final Assertion listProperty = Assertion.createObjectPropertyAssertion(attribute.getIRI().toURI(), inferred); 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 f7986ca22..d7cce1386 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 @@ -48,16 +48,15 @@ public interface ObjectOntologyMapper { T loadEntity(LoadingParameters loadingParameters); /** - * Loads a reference to an entity corresponding to the specified parameters. + * Gets a reference to an entity corresponding to the specified parameters. *

- * The reference is usually an empty object with attributes being loaded lazily. However, it may be also be - * retrieved from the cache, in which case its attributes will be loaded. + * The reference is an empty object with state fetched upon first access. * * @param loadingParameters Reference loading parameters * @param Entity type - * @return Loaded entity reference or {@code null} if there is none such + * @return Entity reference */ - T loadReference(LoadingParameters loadingParameters); + T getReference(LoadingParameters loadingParameters); /** * Loads entity field value and sets it on the specified entity. @@ -126,8 +125,8 @@ Set> getAttributeAxioms(T entity, FieldSpecification * Checks if the specified attribute value of the specified entity is inferred in the repository. *

* Note that attribute context may be ignored by the underlying repository, based on its inference storing strategy. - * Also note that if the corresponding statement is both inferred and asserted, this method will return {@code - * true}. + * Also note that if the corresponding statement is both inferred and asserted, this method will return + * {@code true}. * * @param entity Entity whose attribute value to examine * @param fieldSpec Field specification representing the property to examine 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 83a984a5b..f54afc4ae 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 @@ -79,6 +79,8 @@ public class ObjectOntologyMapperImpl implements ObjectOntologyMapper, EntityMap private final EntityInstanceLoader defaultInstanceLoader; private final EntityInstanceLoader twoStepInstanceLoader; + private final EntityReferenceFactory referenceFactory; + public ObjectOntologyMapperImpl(AbstractUnitOfWork uow, Connection connection) { this.uow = Objects.requireNonNull(uow); this.storageConnection = Objects.requireNonNull(connection); @@ -98,6 +100,7 @@ public ObjectOntologyMapperImpl(AbstractUnitOfWork uow, Connection connection) { .descriptorFactory(descriptorFactory) .entityBuilder(entityBuilder).cache(getCache()) .loadStateRegistry(uow.getLoadStateRegistry()).build(); + this.referenceFactory = new EntityReferenceFactory(uow.getMetamodel(), uow); } private CacheManager getCache() { @@ -146,15 +149,10 @@ private T loadEntityInternal(LoadingParameters loadingParameters) { } @Override - public T loadReference(LoadingParameters loadingParameters) { + public T getReference(LoadingParameters loadingParameters) { assert loadingParameters != null; - final IdentifiableEntityType et = getEntityType(loadingParameters.getEntityClass()); - if (et.hasSubtypes()) { - return twoStepInstanceLoader.loadReference(loadingParameters); - } else { - return defaultInstanceLoader.loadReference(loadingParameters); - } + return referenceFactory.createReferenceProxy(loadingParameters); } @Override @@ -238,7 +236,7 @@ private void persistPendingReferences(T instance, NamedResource identifier) final ListValueDescriptor desc = list.getDescriptor(); ListPropertyStrategy.addIndividualsToDescriptor(desc, list.getValues(), et); if (desc instanceof SimpleListValueDescriptor) { - // TODO This can be an update or a persist + // This can be a persist or an update, calling update works for both storageConnection.lists().updateSimpleList((SimpleListValueDescriptor) desc); } else { storageConnection.lists().updateReferencedList((ReferencedListValueDescriptor) desc); 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 7d7776892..4e0802148 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 @@ -17,10 +17,8 @@ */ package cz.cvut.kbss.jopa.oom; -import cz.cvut.kbss.jopa.exception.InstantiationException; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; -import cz.cvut.kbss.jopa.oom.exception.EntityReconstructionException; import cz.cvut.kbss.jopa.oom.metamodel.PolymorphicEntityTypeResolver; import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; @@ -50,19 +48,6 @@ T loadEntity(LoadingParameters loadingParameters) { } } - @Override - T loadReference(LoadingParameters loadingParameters) { - final IdentifiableEntityType rootEt = metamodel.entity(loadingParameters.getEntityClass()); - try { - final IdentifiableEntityType et = resolveEntityType(loadingParameters, rootEt); - return et != null ? entityBuilder.createEntityInstance(loadingParameters.getIdentifier(), et) : null; - } catch (OntoDriverException e) { - throw new StorageAccessException(e); - } catch (InstantiationException e) { - throw new EntityReconstructionException(e); - } - } - private IdentifiableEntityType resolveEntityType(LoadingParameters loadingParameters, IdentifiableEntityType rootEt) throws OntoDriverException { NamedResource individual = NamedResource.create(loadingParameters.getIdentifier()); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectCollection.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectCollection.java index 4edb73f6d..dc31bd38e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectCollection.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectCollection.java @@ -55,7 +55,7 @@ protected ChangeTrackingIndirectCollection(Object owner, Field f, UnitOfWork per protected void persistChange() { assert persistenceContext != null; - if (persistenceContext.isInTransaction() && !persistenceContext.isInCommit()) { + if (persistenceContext.isInTransaction() && !persistenceContext.isFlushingChanges()) { persistenceContext.attributeChanged(owner, field); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMultilingualString.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMultilingualString.java index 086d907d4..3373d2390 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMultilingualString.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/change/ChangeTrackingIndirectMultilingualString.java @@ -56,7 +56,7 @@ public ChangeTrackingIndirectMultilingualString(Object owner, Field f, UnitOfWor private void notifyPersistenceContext() { assert persistenceContext != null; - if (persistenceContext.isInTransaction() && !persistenceContext.isInCommit()) { + if (persistenceContext.isInTransaction() && !persistenceContext.isFlushingChanges()) { persistenceContext.attributeChanged(owner, field); } } 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 index 4f99b9e47..8c5cb2739 100644 --- 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 @@ -15,11 +15,11 @@ */ abstract class LazyLoadingCollectionProxy, E> implements LazyLoadingProxy, Collection { - protected final transient O owner; - protected final transient FieldSpecification fieldSpec; - protected final transient UnitOfWork persistenceContext; + protected final O owner; + protected final FieldSpecification fieldSpec; + protected final UnitOfWork persistenceContext; - private transient T value; + private T value; public LazyLoadingCollectionProxy(O owner, FieldSpecification fieldSpec, UnitOfWork persistenceContext) { 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 index d54abe8f2..a30c289a9 100644 --- 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 @@ -18,9 +18,9 @@ */ public class LazyLoadingMapProxy implements LazyLoadingProxy>, Map { - protected final transient T owner; - protected final transient FieldSpecification> fieldSpec; - protected final transient UnitOfWork persistenceContext; + protected final T owner; + protected final FieldSpecification> fieldSpec; + protected final UnitOfWork persistenceContext; private Map value; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGenerator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGenerator.java index d958e2a82..be6d2a1bc 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGenerator.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/lazy/gen/LazyLoadingEntityProxyGenerator.java @@ -72,6 +72,10 @@ public Class generate(Class entityClass) { public static class GetterInterceptor { + private GetterInterceptor() { + throw new AssertionError(); + } + @RuntimeType public static Object get(@This LazyLoadingEntityProxy proxy, @Origin Method getter) { final Object loaded = proxy.triggerLazyLoading(); @@ -85,6 +89,10 @@ public static Object get(@This LazyLoadingEntityProxy proxy, @Origin Meth public static class SetterInterceptor { + private SetterInterceptor() { + throw new AssertionError(); + } + public static void set(@This LazyLoadingEntityProxy proxy, @Origin Method setter, @AllArguments Object[] args) { final Object loaded = proxy.triggerLazyLoading(); @@ -98,6 +106,10 @@ public static void set(@This LazyLoadingEntityProxy proxy, @Origin Method public static class ProxyMethodsInterceptor { + private ProxyMethodsInterceptor() { + throw new AssertionError(); + } + public static boolean isLoaded(@This LazyLoadingEntityProxy proxy, @FieldValue("value") T value) { return value != null; } 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 index 85d6ab388..4d51dd5b1 100644 --- 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 @@ -17,9 +17,9 @@ * * @param MethodDescription */ -class PersistentPropertyGetterMatcher extends PersistentPropertyMatcher { +public class PersistentPropertyGetterMatcher extends PersistentPropertyMatcher { - PersistentPropertyGetterMatcher(Class parentType) { + public PersistentPropertyGetterMatcher(Class parentType) { super(parentType); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxy.java new file mode 100644 index 000000000..e1cf32fb3 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxy.java @@ -0,0 +1,27 @@ +package cz.cvut.kbss.jopa.proxy.reference; + +import cz.cvut.kbss.jopa.exception.LazyLoadingException; +import cz.cvut.kbss.jopa.exceptions.EntityNotFoundException; +import cz.cvut.kbss.jopa.utils.IdentifierTransformer; + +public interface EntityReferenceProxy extends EntityReferenceProxyPropertyAccessor { + + default T triggerLoading() { + if (isLoaded()) { + return getValue(); + } + if (getPersistenceContext() == null || !getPersistenceContext().isActive()) { + throw new LazyLoadingException("No active persistence context is available to load reference of type " + getType() + " with identifier + " + getIdentifier()); + } + final T loaded = getPersistenceContext().readObject(getType(), getIdentifier(), getDescriptor()); + if (loaded == null) { + throw new EntityNotFoundException("Entity '" + getType().getSimpleName() + "' with id " + IdentifierTransformer.stringifyIri(getIdentifier()) + " not found in the repository."); + } + setValue(loaded); + return loaded; + } + + default boolean isLoaded() { + return getValue() != null; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxyGenerator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxyGenerator.java new file mode 100644 index 000000000..8871d0289 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxyGenerator.java @@ -0,0 +1,115 @@ +package cz.cvut.kbss.jopa.proxy.reference; + +import cz.cvut.kbss.jopa.exception.LazyLoadingException; +import cz.cvut.kbss.jopa.exceptions.AttributeModificationForbiddenException; +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.model.metamodel.AnnotatedAccessor; +import cz.cvut.kbss.jopa.model.metamodel.gen.PersistenceContextAwareClassGenerator; +import cz.cvut.kbss.jopa.model.metamodel.gen.PersistentPropertySetterMatcher; +import cz.cvut.kbss.jopa.proxy.lazy.gen.PersistentPropertyGetterMatcher; +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.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.net.URI; +import java.util.Objects; + +import static net.bytebuddy.matcher.ElementMatchers.isGetter; +import static net.bytebuddy.matcher.ElementMatchers.isSetter; + +public class EntityReferenceProxyGenerator implements PersistenceContextAwareClassGenerator { + + private static final Logger LOG = LoggerFactory.getLogger(EntityReferenceProxyGenerator.class); + + private final ByteBuddy byteBuddy = new ByteBuddy().with(new NamingStrategy.AbstractBase() { + @Override + protected String name(TypeDescription typeDescription) { + return typeDescription.getSimpleName() + "_ReferenceProxy"; + } + }); + + @Override + public Class generate(Class entityClass) { + Objects.requireNonNull(entityClass); + LOG.trace("Generating reference proxy for entity class {}.", entityClass); + DynamicType.Unloaded typeDef = byteBuddy.subclass(entityClass) + .annotateType(new GeneratedEntityReferenceProxyImpl()) + .defineField("identifier", URI.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT) + .defineField("type", Class.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT) + .defineField("descriptor", Descriptor.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT) + .defineField("persistenceContext", UnitOfWork.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(EntityReferenceProxyPropertyAccessor.class, entityClass) + .build()) + .intercept(FieldAccessor.ofBeanProperty()) + .implement(EntityReferenceProxy.class) + .method(isSetter().and(new PersistentPropertySetterMatcher<>(entityClass))) + .intercept(MethodDelegation.to(SetterInterceptor.class)) + .method(isGetter().and(new PersistentPropertyGetterMatcher<>(entityClass))) + .intercept(MethodDelegation.to(GetterInterceptor.class)) + .make(); + LOG.debug("Generated dynamic type {} for entity class {}.", typeDef, entityClass); + return typeDef.load(getClass().getClassLoader()).getLoaded(); + } + + public static class GetterInterceptor { + + private GetterInterceptor() { + throw new AssertionError(); + } + + @RuntimeType + public static Object get(@This EntityReferenceProxy proxy, @Origin Method getter) { + if (isIdentifierField(proxy, getter)) { + return proxy.getIdentifier(); + } + final Object loaded = proxy.triggerLoading(); + try { + return getter.invoke(loaded); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new LazyLoadingException("Unable to invoke getter after loading referenced object.", e); + } + } + } + + private static boolean isIdentifierField(EntityReferenceProxy proxy, Method accessor) { + final String fieldName = AnnotatedAccessor.from(accessor).getPropertyName(); + return proxy.getPersistenceContext().getMetamodel().entity(proxy.getType()).getIdentifier().getName() + .equals(fieldName); + } + + public static class SetterInterceptor { + + private SetterInterceptor() { + throw new AssertionError(); + } + + public static void set(@This EntityReferenceProxy proxy, @Origin Method setter, + @AllArguments Object[] args) { + if (isIdentifierField(proxy, setter)) { + throw new AttributeModificationForbiddenException("Cannot change entity reference identifier."); + } + final Object loaded = proxy.triggerLoading(); + try { + setter.invoke(loaded, args); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new LazyLoadingException("Unable to invoke setter after loading referenced object.", e); + } + } + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxyPropertyAccessor.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxyPropertyAccessor.java new file mode 100644 index 000000000..fc143f368 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxyPropertyAccessor.java @@ -0,0 +1,34 @@ +package cz.cvut.kbss.jopa.proxy.reference; + +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.sessions.UnitOfWork; + +import java.net.URI; + +/** + * Provides access to persistence context-related attributes needed by entity reference proxies. + *

+ * This interface should be implemented by the classes generated by {@link EntityReferenceProxyGenerator}. + */ +public interface EntityReferenceProxyPropertyAccessor { + + URI getIdentifier(); + + void setIdentifier(URI identifier); + + Class getType(); + + void setType(Class entityType); + + Descriptor getDescriptor(); + + void setDescriptor(Descriptor descriptor); + + UnitOfWork getPersistenceContext(); + + void setPersistenceContext(UnitOfWork uow); + + T getValue(); + + void setValue(T value); +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/GeneratedEntityReferenceProxy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/GeneratedEntityReferenceProxy.java new file mode 100644 index 000000000..a534e8020 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/GeneratedEntityReferenceProxy.java @@ -0,0 +1,14 @@ +package cz.cvut.kbss.jopa.proxy.reference; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker interface for generated entity reference proxy classes. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface GeneratedEntityReferenceProxy { +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/GeneratedEntityReferenceProxyImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/GeneratedEntityReferenceProxyImpl.java new file mode 100644 index 000000000..02a42a777 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/proxy/reference/GeneratedEntityReferenceProxyImpl.java @@ -0,0 +1,10 @@ +package cz.cvut.kbss.jopa.proxy.reference; + +import java.lang.annotation.Annotation; + +public class GeneratedEntityReferenceProxyImpl implements GeneratedEntityReferenceProxy { + @Override + public Class annotationType() { + return GeneratedEntityReferenceProxy.class; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryHints.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryHints.java index ec9be2808..b4797e014 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryHints.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryHints.java @@ -33,10 +33,10 @@ public class QueryHints { * By target ontology, it is meant either the shared ontology, which does not contain pending transactional changes, * or the transactional ontology (w.r.t. the persistence context issuing the query), where transactional changes may * influence the query results. - * + *

* Note that OntoDriver implementations may choose to ignore the selection depending on their internal transaction * and query execution mechanism. - * + *

* Valid values are {@literal CENTRAL} and {@literal TRANSACTIONAL}. */ public static final String TARGET_ONTOLOGY = "cz.cvut.kbss.jopa.query.targetOntology"; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/AttributeNode.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/AttributeNode.java index ad7a03424..bffa860f0 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/AttributeNode.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/AttributeNode.java @@ -66,4 +66,9 @@ public boolean requiresFilterExpression() { public String toFilterExpression(String filterParam, String filterValue) { return filterParam; } + + @Override + public String toString() { + return getValue() + (getChild() != null ? "." + getChild() : ""); + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/FunctionNode.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/FunctionNode.java index a49d3c77d..7fcd0f879 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/FunctionNode.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/FunctionNode.java @@ -79,4 +79,9 @@ public String toFilterExpression(String filterParam, String filterValue) { return SoqlFunctionTranslator.getSparqlFunction(functionName) + "(" + child.toFilterExpression(filterParam, filterValue) + ")"; } + + @Override + public String toString() { + return functionName + "(" + (child != null ? child.toString() : "") + ")"; + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlAttribute.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlAttribute.java index 0cf4f81da..87f520b41 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlAttribute.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlAttribute.java @@ -169,4 +169,9 @@ private static String toIri(SoqlNode node) { final String nodeIri = node.getIri(); return SparqlConstants.RDF_TYPE_SHORTCUT.equals(nodeIri) ? nodeIri : IdentifierTransformer.stringifyIri(nodeIri); } + + @Override + public String toString() { + return (value != null ? value + "." : "") + (getFirstNode() != null ? getFirstNode().toString() : " "); + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlParameter.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlParameter.java index 24826b64f..bc2296ce6 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlParameter.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlParameter.java @@ -57,4 +57,9 @@ public String getAsValue(String rootVariable) { } return buildParam.toString(); } + + @Override + public String toString() { + return firstNode != null ? firstNode.toString() : getClass().getSimpleName(); + } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java index bce566df3..0d6024b1a 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java @@ -135,6 +135,10 @@ private void pushNewAttribute(SoqlAttribute myAttr) { this.attrPointer = myAttr; } + private void popAttribute() { + this.attrPointer = attributes.remove(attributes.size() - 1); + } + @Override public void exitJoinedParams(SoqlParser.JoinedParamsContext ctx) { } @@ -192,15 +196,18 @@ public void enterObjWithAttr(SoqlParser.ObjWithAttrContext ctx) { SoqlNode objectNode = new AttributeNode(owner); SoqlNode attributeNode = new AttributeNode(objectNode, attribute); objectNode.setChild(attributeNode); + setIris(objectNode); + SoqlAttribute newAttr = new SoqlAttribute(objectNode); if (isIdentifier(objectNode, attributeNode)) { this.isInObjectIdentifierExpression = true; - if (projectedVariable.equals(objectNode.getValue())) { + if (projectedVariable.equals(objectNode.getValue()) && currentPointerIsNotAttributeReference()) { attrPointer.setProjected(true); + } else { + newAttr.setProjected(true); + pushNewAttribute(newAttr); } } else { - setIris(objectNode); - SoqlAttribute myAttr = new SoqlAttribute(objectNode); - pushNewAttribute(myAttr); + pushNewAttribute(newAttr); } } @@ -216,6 +223,10 @@ private boolean isIdentifier(SoqlNode objectNode, SoqlNode attributeNode) { return entityType.getIdentifier().getName().equals(attributeNode.getValue()); } + private boolean currentPointerIsNotAttributeReference() { + return !attrPointer.getFirstNode().hasChild(); + } + @Override public void exitObjWithAttr(SoqlParser.ObjWithAttrContext ctx) { } @@ -402,10 +413,16 @@ public void exitComparisonExpression(SoqlParser.ComparisonExpressionContext ctx) if (attrPointer.isProjected()) { this.rootVariable = SoqlUtils.soqlVariableToSparqlVariable(whereClauseValue.getText()); } - if (attributes.size() > 1 && !attrPointer.getFirstNode().getChild().hasChild()) { - final String varName = whereClauseValue.getText(); - attrPointer.getFirstNode().getChild().setValue( - varName.charAt(0) == SoqlConstants.VARIABLE_PREFIX ? varName.substring(1) : varName); + if (attributes.size() > 1) { + if (isRootIdentifier(attrPointer)) { + // If the current attribute is identifier (i.e., something like root.iri = :iri) and there are other attributes to work with, + // just pop it and not use it in the query anymore + popAttribute(); + } else { + final String varName = whereClauseValue.getText(); + attrPointer.getFirstNode().getChild().setValue( + varName.charAt(0) == SoqlConstants.VARIABLE_PREFIX ? varName.substring(1) : varName); + } } } else { attrPointer.setOperator(new ComparisonOperator(operator)); @@ -414,6 +431,11 @@ public void exitComparisonExpression(SoqlParser.ComparisonExpressionContext ctx) this.isInObjectIdentifierExpression = false; } + private boolean isRootIdentifier(SoqlAttribute attribute) { + return isIdentifier(attribute.getFirstNode(), attribute.getFirstNode().getChild()) && + !attribute.getFirstNode().getChild().hasChild(); + } + @Override public void enterTables(SoqlParser.TablesContext ctx) { } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java index 868c185fe..2e3fc9c1a 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java @@ -83,6 +83,7 @@ public abstract class AbstractUnitOfWork extends AbstractSession implements Unit final Map deletedObjects; final Map newObjectsCloneToOriginal; final Map newObjectsKeyToClone = new HashMap<>(); + private final Map referenceProxies; RepositoryMap repoMap; final LoadStateDescriptorRegistry loadStateRegistry; @@ -93,7 +94,7 @@ public abstract class AbstractUnitOfWork extends AbstractSession implements Unit private boolean transactionActive; private boolean isActive; - private boolean inCommit; + private boolean flushingChanges; UnitOfWorkChangeSet uowChangeSet = ChangeSetFactory.createUoWChangeSet(); @@ -113,6 +114,7 @@ public AbstractUnitOfWork(AbstractSession parent, Configuration configuration) { this.cloneMapping = cloneToOriginals.keySet(); this.deletedObjects = new IdentityHashMap<>(); this.newObjectsCloneToOriginal = new IdentityHashMap<>(); + this.referenceProxies = new IdentityHashMap<>(); this.repoMap = new RepositoryMap(); this.loadStateRegistry = new LoadStateDescriptorRegistry(this::stringify); this.indirectWrapperHelper = new IndirectWrapperHelper(this); @@ -185,7 +187,6 @@ public void commit() { if (!isActive()) { throw new IllegalStateException("Cannot commit inactive Unit of Work!"); } - this.inCommit = true; commitUnitOfWork(); LOG.trace("UnitOfWork commit finished."); } @@ -194,11 +195,23 @@ public void commit() { * Commit this Unit of Work. */ private void commitUnitOfWork() { + this.flushingChanges = true; commitToStorage(); mergeChangesIntoParent(); postCommit(); } + 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()); + } + } + } + /** * If there are any changes, commit them to the ontology. */ @@ -228,7 +241,7 @@ private void evictPossiblyUpdatedReferencesFromCache() { private void postCommit() { final boolean changes = hasChanges(); clear(); - this.inCommit = false; + this.flushingChanges = false; if (changes) { getLiveObjectCache().evictInferredObjects(); } @@ -307,8 +320,22 @@ 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); + Objects.requireNonNull(cls); + Objects.requireNonNull(identifier); + Objects.requireNonNull(descriptor); + + final T managed = readManagedObject(cls, identifier, descriptor); + if (managed != null) { + return managed; + } + if (keysToClones.containsKey(identifier)) { + throw new EntityNotFoundException("Entity '" + cls.getSimpleName() + "' with id " + IdentifierTransformer.stringifyIri(identifier) + " not found."); + } + final T reference = storage.getReference(new LoadingParameters<>(cls, getValueAsURI(identifier), descriptor)); + registerEntityWithOntologyContext(reference, descriptor); + referenceProxies.put(reference, reference); + loadStateRegistry.put(reference, LoadStateDescriptorFactory.createNotLoaded(reference, entityType(cls))); + return reference; } @Override @@ -332,7 +359,7 @@ public EntityState getState(Object entity) { return EntityState.REMOVED; } else if (newObjectsCloneToOriginal.containsKey(entity)) { return EntityState.MANAGED_NEW; - } else if (cloneMapping.contains(entity)) { + } else if (cloneMapping.contains(entity) || referenceProxies.containsKey(entity)) { return EntityState.MANAGED; } else { return EntityState.NOT_MANAGED; @@ -348,7 +375,7 @@ public EntityState getState(Object entity, Descriptor descriptor) { return EntityState.REMOVED; } else if (newObjectsCloneToOriginal.containsKey(entity) && isInRepository(descriptor, entity)) { return EntityState.MANAGED_NEW; - } else if (cloneMapping.contains(entity) && isInRepository(descriptor, entity)) { + } else if ((cloneMapping.contains(entity) || referenceProxies.containsKey(entity)) && isInRepository(descriptor, entity)) { return EntityState.MANAGED; } else { return EntityState.NOT_MANAGED; @@ -364,7 +391,12 @@ public boolean isObjectNew(Object entity) { public boolean isObjectManaged(Object entity) { Objects.requireNonNull(entity); - return cloneMapping.contains(entity) && !deletedObjects.containsKey(entity) || newObjectsCloneToOriginal.containsKey(entity); + return (cloneMapping.contains(entity) || isManagedReference(entity)) && !deletedObjects.containsKey(entity) + || newObjectsCloneToOriginal.containsKey(entity); + } + + private boolean isManagedReference(Object entity) { + return referenceProxies.containsKey(entity); } @Override @@ -420,7 +452,7 @@ public Object registerExistingObject(Object entity, CloneRegistrationDescriptor return getCloneForOriginal(entity); } final CloneConfiguration cloneConfig = CloneConfiguration.withDescriptor(registrationDescriptor.getDescriptor()) - .forPersistenceContext(!isInCommit()) + .forPersistenceContext(!isFlushingChanges()) .addPostRegisterHandlers(registrationDescriptor.getPostCloneHandlers()); Object clone = cloneBuilder.buildClone(entity, cloneConfig); assert clone != null; @@ -466,7 +498,11 @@ private void calculateNewObjects(UnitOfWorkChangeSet changeSet) { private void calculateDeletedObjects(final UnitOfWorkChangeSet changeSet) { for (Object clone : deletedObjects.keySet()) { final Descriptor descriptor = getDescriptor(clone); - final Object original = getOriginal(clone); + Object original = getOriginal(clone); + if (original == null) { + assert referenceProxies.containsKey(clone); + original = clone; + } changeSet.addDeletedObjectChangeSet(ChangeSetFactory.createDeleteObjectChange(clone, original, descriptor)); changeSet.cancelObjectChanges(original); } @@ -515,7 +551,7 @@ public Object getOriginal(Object clone) { * @param original Original to register */ public void registerOriginalForNewClone(Object clone, Object original) { - assert inCommit; + assert flushingChanges; assert newObjectsCloneToOriginal.containsKey(clone); newObjectsCloneToOriginal.put(clone, original); } @@ -642,8 +678,9 @@ public void refreshObject(T object) { uowChangeSet.cancelObjectChanges(getOriginal(object)); T original = connection.find(params); if (original == null) { - throw new EntityNotFoundException("Entity " + object + " no longer exists in the repository."); + throw new EntityNotFoundException("Entity " + stringify(object) + " no longer exists in the repository."); } + removeLazyLoadingProxies(object); T source = (T) cloneBuilder.buildClone(original, CloneConfiguration.withDescriptor(descriptor)); final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(source, object, descriptor); changeCalculator.calculateChanges(chSet); @@ -772,8 +809,8 @@ public boolean isInTransaction() { } @Override - public boolean isInCommit() { - return inCommit; + public boolean isFlushingChanges() { + return flushingChanges; } @Override 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 index 8fa4216ce..fa8eac962 100644 --- 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 @@ -131,7 +131,7 @@ void registerClone(Object clone, Object original, Descriptor descriptor) { } private void attachPersistenceContextToEntity(Object entity) { - if (isInCommit()) { + if (isFlushingChanges()) { return; } assert entity instanceof Manageable; 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 bbc75e061..0e5785a43 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 @@ -62,7 +62,7 @@ public T find(LoadingParameters loadingParameters) { } public T getReference(LoadingParameters loadingParameters) { - return mapper.loadReference(loadingParameters); + return mapper.getReference(loadingParameters); } public void merge(T entity, FieldSpecification fieldSpec, Descriptor descriptor) { 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 index ca7fd9f9b..1854926e1 100644 --- 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 @@ -3,10 +3,8 @@ 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; @@ -27,17 +25,6 @@ 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(); @@ -55,6 +42,7 @@ void commitToStorage() { chSet.getChanges() .forEach(record -> { AttributeModificationValidator.verifyCanModify(record.getAttribute()); + preventCachingIfReferenceIsNotLoaded(record); storage.merge(entity, (FieldSpecification) record.getAttribute(), chSet.getDescriptor()); }); et.getLifecycleListenerManager().invokePostUpdateCallbacks(entity); @@ -112,12 +100,6 @@ T mergeDetachedInternal(T toMerge, Descriptor descriptor) { 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; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java index bfab6fa77..87a854405 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/UnitOfWork.java @@ -85,11 +85,11 @@ public interface UnitOfWork extends ConfigurationHolder, MetamodelProvider, Wrap boolean isInTransaction(); /** - * Returns true if this Unit of Work is currently committing changes to the repository. + * Returns true if this Unit of Work is currently flushing changes to the repository. * - * @return {@code true} if the UoW is in commit, {@code false} otherwise + * @return {@code true} if the UoW is flushing changes, {@code false} otherwise */ - boolean isInCommit(); + boolean isFlushingChanges(); /** * Return true if the given entity is managed. 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 index e66b80cc8..45b06a59e 100644 --- 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 @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.sessions.change; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.utils.MetamodelUtils; import java.net.URI; @@ -31,12 +32,14 @@ public interface Change { * * @return Object type */ - Class getObjectClass(); + default Class getObjectClass() { + return MetamodelUtils.getEntityClass(getClone().getClass()); + } /** * Gets the clone with changes. * - * @return Clone + * @return Clone, never {@code null} */ Object getClone(); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeCalculator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeCalculator.java index eb04dcf74..533aaea39 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeCalculator.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ChangeCalculator.java @@ -19,10 +19,9 @@ import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; 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.utils.EntityPropertiesUtils; +import cz.cvut.kbss.jopa.utils.JOPALazyUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -141,7 +140,7 @@ private boolean calculateChangesInternal(ObjectChangeSet changeSet) { } Object clVal = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), clone); Object origVal = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), original); - if (shouldSkipLazyLoadedField(origVal, clVal)) { + if (JOPALazyUtils.isLazyLoadingProxy(clVal)) { continue; } boolean changed = valueChanged(origVal, clVal); @@ -152,9 +151,4 @@ private boolean calculateChangesInternal(ObjectChangeSet changeSet) { } 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/DeleteObjectChange.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/DeleteObjectChange.java index 27569db1c..d92993fdb 100644 --- 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 @@ -38,11 +38,6 @@ public DeleteObjectChange(Object clone, Object original, Descriptor descriptor) this.descriptor = Objects.requireNonNull(descriptor); } - @Override - public Class getObjectClass() { - return clone.getClass(); - } - @Override public Object getClone() { 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 index 03f27c6bc..ef125bff6 100644 --- 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 @@ -18,11 +18,6 @@ public NewObjectChange(Object object, Descriptor descriptor) { this.descriptor = Objects.requireNonNull(descriptor); } - @Override - public Class getObjectClass() { - return object.getClass(); - } - @Override public Object getClone() { return object; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSet.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSet.java index cfbf0f8ab..5cbf88ccd 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSet.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/change/ObjectChangeSet.java @@ -71,11 +71,6 @@ public boolean hasChanges() { return !attributesToChange.isEmpty(); } - @Override - public Class getObjectClass() { - return cloneObject.getClass(); - } - @Override public Object getOriginal() { return changedObject; 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 decd4bde9..f352ca1c8 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 @@ -43,6 +43,9 @@ Object getValueToSet(Object mergedValue, Descriptor descriptor) { if (mergedValue == null) { return null; } + if (uow.contains(mergedValue)) { + return mergedValue; + } final Object identifier = EntityPropertiesUtils.getIdentifier(mergedValue, uow.getMetamodel()); if (identifier == null) { return mergedValue; 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 index 5e66febe1..916ed8a43 100644 --- 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 @@ -44,8 +44,12 @@ public List> getPostCloneHandlers() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof CloneRegistrationDescriptor that)) return false; + 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()); 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 index 2dd33d2d8..4a77e4249 100644 --- 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 @@ -10,6 +10,20 @@ */ public class JOPALazyUtils { + private JOPALazyUtils() { + throw new AssertionError(); + } + + /** + * Checks if this specified object is a lazy loading proxy (instance of {@link LazyLoadingProxy}). + * + * @param object Object to investigate + * @return {@code true} if argument is lazy loading proxy, {@code false} otherwise + */ + public static boolean isLazyLoadingProxy(Object object) { + return object instanceof LazyLoadingProxy; + } + /** * Triggers loading on the specified lazy loading proxy. *

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 a357dbe6f..cc57936c3 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 @@ -19,6 +19,7 @@ import cz.cvut.kbss.jopa.model.metamodel.Metamodel; import cz.cvut.kbss.jopa.model.metamodel.gen.GeneratedEntityClass; +import cz.cvut.kbss.jopa.proxy.reference.GeneratedEntityReferenceProxy; import java.net.URI; import java.util.Collection; @@ -66,6 +67,7 @@ public static void checkForModuleSignatureExtension(Collection types, Metamod * @return Entity class */ public static Class getEntityClass(Class cls) { - return cls.getAnnotation(GeneratedEntityClass.class) != null ? cls.getSuperclass() : cls; + return (cls.getAnnotation(GeneratedEntityClass.class) != null || cls.getAnnotation(GeneratedEntityReferenceProxy.class) != null) + ? cls.getSuperclass() : cls; } } diff --git a/jopa-impl/src/main/resources/jopa.properties b/jopa-impl/src/main/resources/jopa.properties new file mode 100644 index 000000000..56ce495df --- /dev/null +++ b/jopa-impl/src/main/resources/jopa.properties @@ -0,0 +1,2 @@ +cz.cvut.jopa.version=${project.version} +cz.cvut.jopa.build.timestamp=${maven.build.timestamp} 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 2f894b003..b569100e8 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 @@ -496,7 +496,6 @@ public static void initOWLClassHMocks(IdentifiableEntityType etMock, 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); when(etMock.getName()).thenReturn(OWLClassH.class.getSimpleName()); when(etMock.getAttributes()).thenReturn( new HashSet<>(Arrays.>asList(clsAMock, clsGMock))); @@ -525,6 +524,8 @@ public static void initOWLClassHMocks(IdentifiableEntityType etMock, when(etMock.getFieldSpecification(clsAMock.getName())).thenReturn(clsAMock); when(etMock.getFieldSpecification(clsGMock.getName())).thenReturn(clsGMock); + when(etMock.getAttribute(clsAMock.getName())).thenReturn(clsAMock); + when(etMock.getAttribute(clsGMock.getName())).thenReturn(clsGMock); when(etMock.getIdentifier()).thenReturn(idMock); when(idMock.getName()).thenReturn(OWLClassH.class.getDeclaredField("uri").getName()); when(idMock.getJavaField()).thenReturn(OWLClassH.class.getDeclaredField("uri")); @@ -585,6 +586,7 @@ public static void initOWLClassJMocks(IdentifiableEntityType etMock, when(setAMock.getBindableJavaType()).thenReturn(OWLClassA.class); when(setAMock.getConstraints()).thenReturn(new ParticipationConstraint[]{}); when(setAMock.getDeclaringType()).thenReturn(etMock); + when(etMock.getFieldSpecification(OWLClassJ.getOwlClassAField().getName())).thenReturn(setAMock); when(etMock.getIdentifier()).thenReturn(idMock); when(idMock.getJavaField()).thenReturn(OWLClassJ.class.getDeclaredField("uri")); when(idMock.getDeclaringType()).thenReturn(etMock); 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 10cae1532..f056cc8cb 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 @@ -33,16 +33,19 @@ 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.descriptors.ObjectPropertyCollectionDescriptor; import cz.cvut.kbss.jopa.model.metamodel.Attribute; 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.proxy.lazy.LazyLoadingSetProxy; 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.UnitOfWork; -import cz.cvut.kbss.jopa.sessions.AbstractUnitOfWork; +import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; import cz.cvut.kbss.jopa.transactions.EntityTransaction; import cz.cvut.kbss.jopa.utils.Configuration; import org.junit.jupiter.api.BeforeEach; @@ -59,6 +62,7 @@ import java.net.URI; import java.util.Collections; import java.util.HashSet; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -133,6 +137,15 @@ void testCascadeMergeOnNullCollection() { // the merged object is correctly passed to merge in UoW } + @Test + void cascadeMergeDoesNothingForLazyLoadingProxy() { + final OWLClassJ j = new OWLClassJ(Generators.createIndividualIdentifier()); + j.setOwlClassA(new LazyLoadingSetProxy<>(j, mocks.forOwlClassJ().setAttribute(), uow)); + + em.merge(j); + verify(uow).mergeDetached(j, new EntityDescriptor()); + } + @Test void mergeDetachedWithSingletonSet() { final OWLClassJ j = new OWLClassJ(); @@ -511,7 +524,7 @@ void removeIsAbleToBreakCascadingCycle() throws Exception { void isLoadedReturnsTrueForEagerlyLoadedAttributeOfManagedInstance() throws Exception { final OWLClassA a = Generators.generateOwlClassAInstance(); doAnswer((invocationOnMock) -> a).when(uow) - .readObject(eq(OWLClassA.class), eq(a.getUri()), any(Descriptor.class)); + .readObject(eq(OWLClassA.class), eq(a.getUri()), any(Descriptor.class)); doReturn(LoadState.LOADED).when(uow).isLoaded(a, OWLClassA.getStrAttField().getName()); final OWLClassA found = em.find(OWLClassA.class, a.getUri()); assertTrue(em.isLoaded(found, OWLClassA.getStrAttField().getName())); @@ -530,7 +543,7 @@ void isLoadedReturnsTrueForNonNullLazilyLoadedAttribute() throws Exception { inst.setUri(Generators.createIndividualIdentifier()); inst.setOwlClassE(new OWLClassE()); doAnswer((invocationOnMock) -> inst).when(uow) - .readObject(eq(OWLClassK.class), eq(inst.getUri()), any(Descriptor.class)); + .readObject(eq(OWLClassK.class), eq(inst.getUri()), any(Descriptor.class)); doReturn(LoadState.LOADED).when(uow).isLoaded(inst, OWLClassK.getOwlClassEField().getName()); final OWLClassK found = em.find(OWLClassK.class, inst.getUri()); assertTrue(em.isLoaded(found, OWLClassK.getOwlClassEField().getName())); @@ -654,4 +667,69 @@ void entityManagerIsAutoCloseable() { } verify(emfMock).entityManagerClosed(any(AbstractEntityManager.class)); } + + @Test + void cascadeDetachDoesNothingWithLazyLoadingProxy() { + final OWLClassJ original = new OWLClassJ(Generators.createIndividualIdentifier()); + original.setOwlClassA(null); + uow.getLoadStateRegistry() + .put(original, LoadStateDescriptorFactory.createNotLoaded(original, mocks.forOwlClassJ().entityType())); + when(connectorMock.find(any(LoadingParameters.class))).thenReturn(original); + final OWLClassJ toDetach = em.find(OWLClassJ.class, original.getUri()); + em.detach(toDetach); + assertNotNull(toDetach.getOwlClassA()); + assertTrue(toDetach.getOwlClassA().isEmpty()); + } + + @Test + void cascadeRefreshLoadsTriggersLazyLoading() { + final OWLClassJ lazyOriginal = new OWLClassJ(Generators.createIndividualIdentifier()); + lazyOriginal.setOwlClassA(null); + uow.getLoadStateRegistry() + .put(lazyOriginal, LoadStateDescriptorFactory.createNotLoaded(lazyOriginal, mocks.forOwlClassJ() + .entityType())); + when(connectorMock.find(new LoadingParameters<>(OWLClassJ.class, lazyOriginal.getUri(), new EntityDescriptor()))).thenReturn(lazyOriginal); + final OWLClassJ loadedOriginal = new OWLClassJ(lazyOriginal.getUri()); + final OWLClassA a = Generators.generateOwlClassAInstance(); + loadedOriginal.setOwlClassA(Collections.singleton(a)); + uow.getLoadStateRegistry() + .put(loadedOriginal, LoadStateDescriptorFactory.createAllLoaded(lazyOriginal, mocks.forOwlClassJ() + .entityType())); + final LoadingParameters refreshLoadParams = new LoadingParameters<>(OWLClassJ.class, lazyOriginal.getUri(), new EntityDescriptor(), true); + refreshLoadParams.bypassCache(); + when(connectorMock.find(refreshLoadParams)).thenReturn(loadedOriginal); + final LoadingParameters refreshALoadParams = new LoadingParameters<>(OWLClassA.class, a.getUri(), new ObjectPropertyCollectionDescriptor(mocks.forOwlClassJ() + .setAttribute()), true); + refreshALoadParams.bypassCache(); + when(connectorMock.find(refreshALoadParams)).thenReturn(a); + + final OWLClassJ toRefresh = em.find(OWLClassJ.class, lazyOriginal.getUri()); + em.refresh(toRefresh); + assertNotNull(toRefresh.getOwlClassA()); + assertEquals(1, toRefresh.getOwlClassA().size()); + } + + @Test + void cascadeRemoveTriggersLazyLoadingAndCascadesRemoval() { + final OWLClassJ lazyOriginal = new OWLClassJ(Generators.createIndividualIdentifier()); + lazyOriginal.setOwlClassA(null); + uow.getLoadStateRegistry() + .put(lazyOriginal, LoadStateDescriptorFactory.createNotLoaded(lazyOriginal, mocks.forOwlClassJ() + .entityType())); + when(connectorMock.find(new LoadingParameters<>(OWLClassJ.class, lazyOriginal.getUri(), new EntityDescriptor()))).thenReturn(lazyOriginal); + final OWLClassJ toRemove = em.find(OWLClassJ.class, lazyOriginal.getUri()); + final OWLClassA a = Generators.generateOwlClassAInstance(); + uow.getLoadStateRegistry() + .put(a, LoadStateDescriptorFactory.createAllLoaded(a, mocks.forOwlClassA().entityType())); + doAnswer(inv -> { + final OWLClassJ target = inv.getArgument(0); + target.setOwlClassA(new HashSet<>(Set.of(a))); + return null; + }).when(connectorMock).loadFieldValue(toRemove, mocks.forOwlClassJ().setAttribute(), new EntityDescriptor()); + + em.remove(toRemove); + verify(connectorMock).remove(toRemove.getUri(), OWLClassJ.class, new EntityDescriptor()); + verify(connectorMock).remove(a.getUri(), OWLClassA.class, new ObjectPropertyCollectionDescriptor(mocks.forOwlClassJ() + .setAttribute())); + } } 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 e4884db89..2867b868a 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 @@ -26,6 +26,7 @@ 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.proxy.reference.EntityReferenceProxy; import cz.cvut.kbss.jopa.query.NamedQueryManager; import cz.cvut.kbss.jopa.utils.Configuration; import cz.cvut.kbss.ontodriver.config.OntoDriverProperties; @@ -715,6 +716,7 @@ void getLazyLoadingProxyReturnsLazyLoadingProxyClassForSpecifiedType() { sut.build(entityClasses); final Class result = sut.getLazyLoadingProxy(OWLClassA.class); assertNotNull(result); + assertTrue(LazyLoadingEntityProxy.class.isAssignableFrom(result)); assertThat(result, typeCompatibleWith(OWLClassA.class)); } @@ -727,4 +729,25 @@ void getLazyLoadingProxyCachesGeneratedProxyClasses() { final Class resultTwo = sut.getLazyLoadingProxy(OWLClassA.class); assertSame(resultOne, resultTwo); } + + @Test + void getEntityReferenceProxyReturnsGeneratedEntityReferenceProxyClassForSpecifiedType() { + final Set> entityClasses = new HashSet<>(List.of(OWLClassA.class)); + final MetamodelImpl sut = new MetamodelImpl(conf); + sut.build(entityClasses); + final Class result = sut.getEntityReferenceProxy(OWLClassA.class); + assertNotNull(result); + assertTrue(EntityReferenceProxy.class.isAssignableFrom(result)); + assertThat(result, typeCompatibleWith(OWLClassA.class)); + } + + @Test + void getEntityReferenceProxyCachesGeneratedProxyClasses() { + final Set> entityClasses = new HashSet<>(List.of(OWLClassA.class)); + final MetamodelImpl sut = new MetamodelImpl(conf); + sut.build(entityClasses); + final Class resultOne = sut.getEntityReferenceProxy(OWLClassA.class); + final Class resultTwo = sut.getEntityReferenceProxy(OWLClassA.class); + assertSame(resultOne, resultTwo); + } } 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 e13e7a024..92994e56d 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 @@ -19,21 +19,15 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.OWLClassD; -import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.exceptions.StorageAccessException; -import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; 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.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; @@ -135,68 +129,6 @@ void loadEntityBypassesCacheWhenConfiguredTo() throws Exception { verify(cacheMock, never()).contains(etAMock.getJavaType(), IDENTIFIER, descriptor); } - @Test - void loadReferenceVerifiesClassAssertionExistenceAndBuildsEntityInstanceWithIdentifier() throws Exception { - final Axiom typeAxiom = - new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createClassAssertion(false), - new Value<>(NamedResource.create( - Vocabulary.c_OwlClassA))); - when(connectionMock.contains(typeAxiom, Collections.emptySet())).thenReturn(true); - when(descriptorFactoryMock.createForReferenceLoading(IDENTIFIER, etAMock)).thenReturn(typeAxiom); - when(entityConstructorMock.createEntityInstance(IDENTIFIER, etAMock)).thenReturn(entityA); - - final OWLClassA result = instanceLoader.loadReference(loadingParameters); - assertNotNull(result); - verify(descriptorFactoryMock).createForReferenceLoading(IDENTIFIER, etAMock); - verify(connectionMock).contains(typeAxiom, Collections.emptySet()); - verify(entityConstructorMock).createEntityInstance(IDENTIFIER, etAMock); - } - - @Test - void loadReferenceChecksForClassAssertionInCorrectContext() throws Exception { - final EntityDescriptor descriptor = new EntityDescriptor(Generators.createIndividualIdentifier()); - final Axiom typeAxiom = - new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createClassAssertion(false), - new Value<>(NamedResource.create( - Vocabulary.c_OwlClassA))); - when(connectionMock.contains(eq(typeAxiom), any())).thenReturn(true); - when(descriptorFactoryMock.createForReferenceLoading(IDENTIFIER, etAMock)).thenReturn(typeAxiom); - when(entityConstructorMock.createEntityInstance(IDENTIFIER, etAMock)).thenReturn(entityA); - - final OWLClassA result = - instanceLoader.loadReference(new LoadingParameters<>(OWLClassA.class, IDENTIFIER, descriptor)); - assertNotNull(result); - verify(connectionMock).contains(typeAxiom, descriptor.getContexts()); - } - - @Test - void loadReferenceReturnsNullWhenStorageDoesNotContainTypeAssertionAxiom() throws Exception { - final Axiom typeAxiom = - new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createClassAssertion(false), - new Value<>(NamedResource.create( - Vocabulary.c_OwlClassA))); - when(connectionMock.contains(any(), any())).thenReturn(false); - when(descriptorFactoryMock.createForReferenceLoading(IDENTIFIER, etAMock)).thenReturn(typeAxiom); - when(entityConstructorMock.createEntityInstance(IDENTIFIER, etAMock)).thenReturn(entityA); - - final OWLClassA result = instanceLoader.loadReference(loadingParameters); - assertNull(result); - verify(entityConstructorMock, never()).createEntityInstance(IDENTIFIER, etAMock); - } - - @Test - void loadReferenceThrowsStorageAccessExceptionOnDriverException() throws Exception { - final Axiom typeAxiom = - new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createClassAssertion(false), - new Value<>(NamedResource.create( - Vocabulary.c_OwlClassA))); - when(descriptorFactoryMock.createForReferenceLoading(IDENTIFIER, etAMock)).thenReturn(typeAxiom); - when(connectionMock.contains(any(), any())).thenThrow(new OntoDriverException()); - - assertThrows(StorageAccessException.class, () -> instanceLoader.loadReference(loadingParameters)); - verify(entityConstructorMock, never()).createEntityInstance(IDENTIFIER, etAMock); - } - @Test void loadEntityReloadsQueryAttributesWhenInstanceIsRetrievedFromCache() { when(cacheMock.contains(OWLClassA.class, loadingParameters.getIdentifier(), descriptor)).thenReturn(true); 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 d0c9f8d5e..eaf475c55 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 @@ -30,7 +30,6 @@ 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; @@ -38,7 +37,10 @@ import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.oom.exception.UnpersistedChangeException; +import cz.cvut.kbss.jopa.proxy.reference.EntityReferenceProxy; +import cz.cvut.kbss.jopa.proxy.reference.EntityReferenceProxyGenerator; import cz.cvut.kbss.jopa.sessions.AbstractUnitOfWork; +import cz.cvut.kbss.jopa.sessions.cache.CacheManager; import cz.cvut.kbss.jopa.sessions.cache.Descriptors; import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor; import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry; @@ -354,7 +356,7 @@ void loadSimpleListThrowsStorageAccessExceptionWhenOntoDriverExceptionIsThrown() } @Test - void loadReferencedListThrowsStorageAccessExceptionWhenOntoDriverExceptionIsThrown() throws Exception { + void getReferencedListThrowsStorageAccessExceptionWhenOntoDriverExceptionIsThrown() throws Exception { final String message = "OntoDriver exception was thrown"; final Lists listsMock = mock(Lists.class); when(listsMock.loadReferencedList(any(ReferencedListDescriptor.class))) @@ -380,7 +382,7 @@ void usesTwoStepInstanceLoaderForLoadingInstanceOfEntityWithSubtypes() throws Ex aDescriptor); doReturn(entity).when(twoStepLoader).loadEntity(loadingParameters); final LoadStateDescriptor loadStateDescriptor = new LoadStateDescriptor<>(entity, mocks.forOwlClassS() - .entityType(), LoadState.UNKNOWN); + .entityType(), LoadState.UNKNOWN); loadStateRegistry.put(entity, loadStateDescriptor); final OWLClassS result = mapper.loadEntity(loadingParameters); @@ -409,7 +411,7 @@ void loadEntityDeterminesConcreteEntityTypeAndLoadsItFromCacheWhenItIsPresentThe 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); + .entityType(), LoadState.UNKNOWN); doReturn(loadStateDescriptor).when(cacheMock).getLoadStateDescriptor(entity); final Types typesMock = mock(Types.class); final NamedResource individual = NamedResource.create(IDENTIFIER); @@ -637,75 +639,25 @@ void persistSavesPendingReferencedListContainingPersistedInstance() throws Excep verify(listsMock).updateReferencedList(any()); } - @Test - void loadReferenceRetrievesEntityReferenceViaDefaultInstanceLoader() throws Exception { - 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))); - when(connectionMock.contains(axiom, Collections.emptySet())).thenReturn(true); - final OWLClassA result = mapper - .loadReference(new LoadingParameters<>(OWLClassA.class, IDENTIFIER, aDescriptor)); - assertNotNull(result); - assertEquals(IDENTIFIER, result.getUri()); - assertNull(result.getStringAttribute()); - verify(connectionMock).contains(eq(axiom), eq(Collections.emptySet())); - } - - @Test - void loadReferenceUsesTwoStepLoaderWhenEntityTypeHasSubclasses() throws Exception { - final Types typesMock = mock(Types.class); - when(connectionMock.types()).thenReturn(typesMock); - final NamedResource idResource = NamedResource.create(IDENTIFIER); - final Set> typesAxioms = Collections.singleton( - new AxiomImpl<>(idResource, Assertion.createClassAssertion(false), - 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); - assertInstanceOf(OWLClassR.class, result); - verify(typesMock).getTypes(eq(idResource), eq(Collections.emptySet()), eq(false)); - } - - @Test - void loadReferenceUsesContextWhenGettingDataFromConnection() throws Exception { - final URI context = Generators.createIndividualIdentifier(); - final EntityDescriptor descriptor = new EntityDescriptor(context); - when(entityConstructorMock.createEntityInstance(IDENTIFIER, mocks.forOwlClassA().entityType())) - .thenReturn(new OWLClassA(IDENTIFIER)); - when(connectionMock.contains( - new AxiomImpl<>(NamedResource.create(IDENTIFIER), Assertion.createClassAssertion(false), - new Value<>(NamedResource.create(Vocabulary.c_OwlClassA))), - Collections.singleton(context))) - .thenReturn(true); - final OWLClassA result = mapper - .loadReference(new LoadingParameters<>(OWLClassA.class, IDENTIFIER, descriptor)); - assertNotNull(result); - verify(connectionMock).contains(any(Axiom.class), eq(Collections.singleton(context))); - } - - @Test - void loadReferenceUsesContextWhenGettingTypesFromConnection() throws Exception { - final URI context = Generators.createIndividualIdentifier(); - final EntityDescriptor descriptor = new EntityDescriptor(context); - final Types typesMock = mock(Types.class); - when(connectionMock.types()).thenReturn(typesMock); - final NamedResource idResource = NamedResource.create(IDENTIFIER); - final Set> typesAxioms = Collections.singleton( - new AxiomImpl<>(idResource, Assertion.createClassAssertion(false), - 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); - verify(typesMock).getTypes(idResource, Collections.singleton(context), false); - } - @Test void getEntityFromCacheOrOntologyThrowsEntityExistsWhenObjectIsAlreadyRegisteredUnderDifferentType() { mapper.registerInstance(IDENTIFIER, entityA); assertThrows(OWLEntityExistsException.class, () -> mapper.getEntityFromCacheOrOntology(OWLClassB.class, IDENTIFIER, aDescriptor)); } + + @Test + void getReferenceReturnsReferenceObjectOfRequestedEntityTypeWithIdentifierSet() throws Exception { + when(metamodelMock.getEntityReferenceProxy(OWLClassA.class)).thenReturn((Class) new EntityReferenceProxyGenerator().generate(OWLClassA.class)); + final OWLClassA result = mapper.getReference(loadingParameters); + assertNotNull(result); + assertEquals(IDENTIFIER, result.getUri()); + assertInstanceOf(EntityReferenceProxy.class, result); + final EntityReferenceProxy proxy = (EntityReferenceProxy) result; + assertEquals(uowMock, proxy.getPersistenceContext()); + assertEquals(aDescriptor, proxy.getDescriptor()); + assertEquals(IDENTIFIER, proxy.getIdentifier()); + assertEquals(OWLClassA.class, proxy.getType()); + verify(connectionMock, never()).find(any()); + } } 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 9ab7cbcb2..87ccdf63d 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 @@ -46,8 +46,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.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.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -133,41 +137,4 @@ void loadEntityThrowsStorageAccessExceptionWhenOntoDriverThrowsException() throw assertThrows(StorageAccessException.class, () -> instanceLoader.loadEntity(loadingParameters)); assertThat(ex.getMessage(), containsString(msg)); } - - @Test - void loadReferenceLoadsReferenceFromStorageWhenEntityTypeIsDetermined() throws Exception { - final Axiom type = new AxiomImpl<>(INDIVIDUAL, Assertion.createClassAssertion(false), - new Value<>(URI.create(OWLClassA.getClassIri()))); - when(typesMock.getTypes(INDIVIDUAL, Collections.emptySet(), false)).thenReturn(Collections.singleton(type)); - when(entityConstructorMock.createEntityInstance(IDENTIFIER, metamodelMock.entity(OWLClassA.class))) - .thenReturn(new OWLClassA(IDENTIFIER)); - - final OWLClassA result = - instanceLoader.loadReference(new LoadingParameters<>(OWLClassA.class, IDENTIFIER, descriptor)); - assertNotNull(result); - verify(typesMock).getTypes(INDIVIDUAL, Collections.emptySet(), false); - } - - @Test - void loadReferenceReturnsNullWhenTypeCannotBeResolved() throws Exception { - when(typesMock.getTypes(INDIVIDUAL, null, false)).thenReturn(Collections.emptySet()); - when(entityConstructorMock.createEntityInstance(IDENTIFIER, metamodelMock.entity(OWLClassA.class))) - .thenReturn(new OWLClassA(IDENTIFIER)); - - final OWLClassA result = - instanceLoader.loadReference(new LoadingParameters<>(OWLClassA.class, IDENTIFIER, descriptor)); - assertNull(result); - verify(entityConstructorMock, never()).createEntityInstance(any(), any()); - } - - @Test - void loadReferenceThrowsStorageAccessExceptionWhenOntoDriverExceptionIsThrown() throws Exception { - final String msg = "Exception message."; - when(typesMock.getTypes(INDIVIDUAL, Collections.emptySet(), false)).thenThrow(new OntoDriverException(msg)); - - final StorageAccessException ex = - assertThrows(StorageAccessException.class, () -> instanceLoader.loadReference(loadingParameters)); - assertThat(ex.getMessage(), containsString(msg)); - verify(entityConstructorMock, never()).createEntityInstance(any(), any()); - } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxyGeneratorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxyGeneratorTest.java new file mode 100644 index 000000000..afd4db5c4 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/proxy/reference/EntityReferenceProxyGeneratorTest.java @@ -0,0 +1,167 @@ +package cz.cvut.kbss.jopa.proxy.reference; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.exceptions.AttributeModificationForbiddenException; +import cz.cvut.kbss.jopa.exceptions.EntityNotFoundException; +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.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.net.URI; +import java.util.List; +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +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 EntityReferenceProxyGeneratorTest { + + @Mock + private UnitOfWork uow; + + private final OWLClassA storedInstance = Generators.generateOwlClassAInstance(); + + private final EntityReferenceProxyGenerator sut = new EntityReferenceProxyGenerator(); + + @Test + void generateGeneratesProxyClassImplementingCorrectInterfaces() { + final Class result = sut.generate(OWLClassA.class); + assertTrue(EntityReferenceProxy.class.isAssignableFrom(result)); + } + + @Test + void generateGeneratesProxyClassThatTriggersLoadingWhenGetterIsAccessedAndReturnsLoadedValue() throws Exception { + final Class result = sut.generate(OWLClassA.class); + final OWLClassA instance = result.getConstructor().newInstance(); + initReferenceLoading(instance); + initMetamodel(); + assertEquals(storedInstance.getStringAttribute(), instance.getStringAttribute()); + verify(uow).readObject(OWLClassA.class, storedInstance.getUri(), null); + } + + private void initReferenceLoading(OWLClassA instance) { + assertInstanceOf(EntityReferenceProxy.class, instance); + final EntityReferenceProxy aInstance = (EntityReferenceProxy) instance; + aInstance.setPersistenceContext(uow); + aInstance.setIdentifier(storedInstance.getUri()); + aInstance.setType(OWLClassA.class); + when(uow.readObject(OWLClassA.class, storedInstance.getUri(), null)).thenReturn(storedInstance); + when(uow.isActive()).thenReturn(true); + } + + private void initMetamodel() throws Exception { + final MetamodelImpl mm = mock(MetamodelImpl.class); + when(uow.getMetamodel()).thenReturn(mm); + final IdentifiableEntityType et = mock(IdentifiableEntityType.class); + when(mm.entity(OWLClassA.class)).thenReturn(et); + final Identifier id = mock(Identifier.class); + when(id.getName()).thenReturn(OWLClassA.class.getDeclaredField("uri").getName()); + when(et.getIdentifier()).thenReturn(id); + } + + @Test + void generateGeneratesProxyClassThatReturnsLoadedValueOnSubsequentGetterCalls() throws Exception { + final Class result = sut.generate(OWLClassA.class); + final OWLClassA instance = result.getConstructor().newInstance(); + initReferenceLoading(instance); + initMetamodel(); + assertEquals(storedInstance.getStringAttribute(), instance.getStringAttribute()); + assertTrue(((EntityReferenceProxy) instance).isLoaded()); + assertEquals(storedInstance.getStringAttribute(), instance.getStringAttribute()); + assertEquals(storedInstance.getStringAttribute(), instance.getStringAttribute()); + verify(uow).readObject(OWLClassA.class, storedInstance.getUri(), null); + } + + @Test + void generateGeneratesProxyClassThatTriggersLoadingWhenSetterIsAccessedAndPassesValueToLoadedInstance() throws Exception { + final Class result = sut.generate(OWLClassA.class); + final OWLClassA instance = result.getConstructor().newInstance(); + initReferenceLoading(instance); + initMetamodel(); + final String newString = "random string " + Generators.randomInt(); + instance.setStringAttribute(newString); + verify(uow).readObject(OWLClassA.class, storedInstance.getUri(), null); + assertEquals(newString, storedInstance.getStringAttribute()); + } + + @Test + void generateGeneratesProxyClassThatUsesLoadedInstanceOnSubsequentSetterCalls() throws Exception { + final Class result = sut.generate(OWLClassA.class); + final OWLClassA instance = result.getConstructor().newInstance(); + initReferenceLoading(instance); + initMetamodel(); + final List values = List.of(Integer.toString(Generators.randomInt()), Integer.toString(Generators.randomInt()), Integer.toString(Generators.randomInt())); + values.forEach(instance::setStringAttribute); + verify(uow).readObject(OWLClassA.class, storedInstance.getUri(), null); + assertEquals(values.get(values.size() - 1), instance.getStringAttribute()); + assertEquals(values.get(values.size() - 1), storedInstance.getStringAttribute()); + } + + @Test + void generateGeneratesProxyClassThatThrowsEntityNotFoundExceptionWhenLoadingOnGetterCallReturnsNull() throws Exception { + initMetamodel(); + final Class result = sut.generate(OWLClassA.class); + final OWLClassA instance = result.getConstructor().newInstance(); + final EntityReferenceProxy aInstance = (EntityReferenceProxy) instance; + aInstance.setPersistenceContext(uow); + aInstance.setIdentifier(storedInstance.getUri()); + aInstance.setType(OWLClassA.class); + when(uow.isActive()).thenReturn(true); + assertThrows(EntityNotFoundException.class, instance::getStringAttribute); + } + + @Test + void generateGeneratesProxyClassThatThrowsEntityNotFoundExceptionWhenLoadingOnSetterCallReturnsNull() throws Exception { + initMetamodel(); + final Class result = sut.generate(OWLClassA.class); + final OWLClassA instance = result.getConstructor().newInstance(); + final EntityReferenceProxy aInstance = (EntityReferenceProxy) instance; + aInstance.setPersistenceContext(uow); + aInstance.setIdentifier(storedInstance.getUri()); + aInstance.setType(OWLClassA.class); + when(uow.isActive()).thenReturn(true); + assertThrows(EntityNotFoundException.class, () -> instance.setTypes(Set.of(Generators.createIndividualIdentifier() + .toString()))); + } + + @Test + void generateGeneratesProxyClassThatDoesNotTriggerLoadingOnIdentifierGetter() throws Exception { + initMetamodel(); + final Class result = sut.generate(OWLClassA.class); + final OWLClassA instance = result.getConstructor().newInstance(); + final EntityReferenceProxy aInstance = (EntityReferenceProxy) instance; + aInstance.setPersistenceContext(uow); + aInstance.setIdentifier(storedInstance.getUri()); + aInstance.setType(OWLClassA.class); + assertEquals(storedInstance.getUri(), instance.getUri()); + verify(uow, never()).readObject(OWLClassA.class, storedInstance.getUri(), null); + } + + // This makes no sense, but let's be sure we are handling it + @Test + void generateGeneratesProxyClassThatThrowsAttributeModificationForbiddenExceptionOnIdentifierSetter() throws Exception { + initMetamodel(); + final Class result = sut.generate(OWLClassA.class); + final OWLClassA instance = result.getConstructor().newInstance(); + final EntityReferenceProxy aInstance = (EntityReferenceProxy) instance; + aInstance.setPersistenceContext(uow); + aInstance.setIdentifier(storedInstance.getUri()); + aInstance.setType(OWLClassA.class); + final URI newId = Generators.createIndividualIdentifier(); + assertThrows(AttributeModificationForbiddenException.class, () -> instance.setUri(newId)); + verify(uow, never()).readObject(OWLClassA.class, storedInstance.getUri(), null); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java index e6e663fa7..58b3c3a90 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java @@ -793,4 +793,14 @@ void parseQueryTranslatesNotMemberOfToFilterNotExists() { final String expectedSparql = "SELECT ?x WHERE { ?x a " + strUri(Vocabulary.c_Person) + " . FILTER NOT EXISTS { ?x a ?disabledType . } }"; parseAndAssertEquality(soql, expectedSparql); } + + /** + * Bug #234 + */ + @Test + void parseQueryTranslatesQueryUsingRootIdentifierAfterReferenceIdentifier() { + final String soql = "SELECT DISTINCT h FROM OWLClassH h WHERE h.owlClassA.uri = :aUri AND h.owlClassG.uri = :gUri AND h.uri = :hUri"; + final String expectedSparql = "SELECT DISTINCT ?hUri WHERE { ?hUri a " + strUri(Vocabulary.c_OwlClassH) + " . ?hUri " + strUri(Vocabulary.p_h_hasA) + " ?aUri . ?hUri " + strUri(Vocabulary.p_h_hasG) + " ?gUri . }"; + parseAndAssertEquality(soql, expectedSparql); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkGetReferenceTestRunner.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkGetReferenceTestRunner.java new file mode 100644 index 000000000..14458be46 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkGetReferenceTestRunner.java @@ -0,0 +1,168 @@ +/* + * 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.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.jopa.exceptions.EntityNotFoundException; +import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException; +import cz.cvut.kbss.jopa.model.EntityState; +import cz.cvut.kbss.jopa.proxy.reference.EntityReferenceProxy; +import cz.cvut.kbss.jopa.proxy.reference.EntityReferenceProxyGenerator; +import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.net.URI; + +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.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public abstract class AbstractUnitOfWorkGetReferenceTestRunner extends UnitOfWorkTestBase { + + @Test + void getReferenceReturnsExistingCloneWhenItIsAlreadyManaged() { + final OWLClassA existing = (OWLClassA) uow.registerExistingObject(entityA, descriptor); + final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + assertNotNull(result); + assertSame(existing, result); + } + + @Test + void getReferenceReturnsExistingNewlyRegisteredObject() { + uow.registerNewObject(entityA, descriptor); + final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + assertNotNull(result); + assertSame(entityA, result); + } + + @Test + void getReferenceThrowsEntityNotFoundExceptionWhenObjectIsScheduledForDeletion() { + final OWLClassA existing = (OWLClassA) uow.registerExistingObject(entityA, descriptor); + uow.removeObject(existing); + assertThrows(EntityNotFoundException.class, () -> uow.getReference(OWLClassA.class, entityA.getUri(), descriptor)); + } + + @Test + void getReferenceThrowsEntityExistsExceptionWhenIndividualIsAlreadyManagedAsDifferentIncompatibleType() { + uow.registerExistingObject(entityA, descriptor); + assertThrows(OWLEntityExistsException.class, + () -> uow.getReference(OWLClassD.class, entityA.getUri(), descriptor)); + } + + @Test + void getReferenceGetsReferenceFromStorageWhenItIsNotManaged() { + final OWLClassA reference = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + assertNotNull(result); + assertEquals(reference.getUri(), result.getUri()); + verify(storageMock).getReference(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor)); + } + + OWLClassA createReference(URI identifier) { + final Class proxyClass = new EntityReferenceProxyGenerator().generate(OWLClassA.class); + try { + final OWLClassA reference = proxyClass.getDeclaredConstructor().newInstance(); + final EntityReferenceProxy proxy = (EntityReferenceProxy) reference; + proxy.setDescriptor(descriptor); + proxy.setType(OWLClassA.class); + proxy.setPersistenceContext(uow); + proxy.setIdentifier(identifier); + final Field idField = OWLClassA.class.getDeclaredField("uri"); + idField.setAccessible(true); + idField.set(reference, identifier); + return reference; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + void containsReturnsTrueForInstanceRetrievedUsingGetReference() { + final OWLClassA reference = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + assertTrue(uow.contains(result)); + } + + @Test + void getStateReturnsManagedForInstanceRetrieveUsingGetReference() { + final OWLClassA reference = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + assertEquals(EntityState.MANAGED, uow.getState(result)); + assertEquals(EntityState.MANAGED, uow.getState(result, descriptor)); + } + + @Test + void removeOfObjectRetrievedUsingGetReferenceEvictsCorrespondingInstanceFromCacheOnCommit() { + when(transactionMock.isActive()).thenReturn(true); + final OWLClassA reference = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + uow.removeObject(result); + uow.commit(); + verify(serverSessionStub.getLiveObjectCache()).evict(OWLClassA.class, reference.getUri(), descriptor.getSingleContext() + .orElse(null)); + } + + @Test + void changesToGetReferenceResultAreMergedIntoOriginalInCacheOnCommit() { + when(transactionMock.isActive()).thenReturn(true); + 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 = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + when(storageMock.find(any(LoadingParameters.class))).thenReturn(entityA); + final OWLClassA a = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + final String strValue = "string value"; + a.setStringAttribute(strValue); + uow.commit(); + assertEquals(strValue, entityA.getStringAttribute()); + } + + @Test + void mergeDetachedMarksChangeRecordForAttributeWithGetReferenceResultAsPreventingCaching() { + when(transactionMock.isActive()).thenReturn(true); + final OWLClassA reference = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + final OWLClassA ref = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + final OWLClassD owner = new OWLClassD(Generators.createIndividualIdentifier()); + final OWLClassD toMerge = new OWLClassD(owner.getUri()); + when(storageMock.find(any(LoadingParameters.class))).thenReturn(owner); + when(storageMock.contains(owner.getUri(), OWLClassD.class, descriptor)).thenReturn(true); + toMerge.setOwlClassA(ref); + + uow.mergeDetached(toMerge, descriptor); + uow.commit(); + + verify(serverSessionStub.getLiveObjectCache(), never()).add(eq(toMerge.getUri()), eq(owner), any()); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java index 9170d6c81..b2e8822e6 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWorkTestRunner.java @@ -794,7 +794,7 @@ void refreshThrowsEntityNotFoundForNonExistentEntity() { when(storageMock.find(loadingParams)).thenReturn(null); final EntityNotFoundException result = assertThrows(EntityNotFoundException.class, () -> uow.refreshObject(d)); - assertThat(result.getMessage(), containsString(d + " no longer exists in the repository")); + assertThat(result.getMessage(), containsString(" no longer exists in the repository")); } @Test diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWorkGetReferenceTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWorkGetReferenceTest.java new file mode 100644 index 000000000..9b2cf51b3 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ChangeTrackingUnitOfWorkGetReferenceTest.java @@ -0,0 +1,67 @@ +package cz.cvut.kbss.jopa.sessions; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.model.EntityState; +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.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.assertFalse; +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; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class ChangeTrackingUnitOfWorkGetReferenceTest extends AbstractUnitOfWorkGetReferenceTestRunner { + + @BeforeEach + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected AbstractUnitOfWork initUnitOfWork() { + return new ChangeTrackingUnitOfWork(serverSessionStub, new Configuration()); + } + + @Test + void attributeChangedPropagatesChangeOfInstanceRetrievedUsingGetReferenceIntoRepository() throws Exception { + when(transactionMock.isActive()).thenReturn(true); + final OWLClassA reference = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + uow.attributeChanged(result, OWLClassA.getStrAttField()); + verify(storageMock).merge(result, metamodelMocks.forOwlClassA().stringAttribute(), descriptor); + } + + @Test + void attributeChangedDoesNotRegisterChangeForMergingIntoCachedOriginalForInstanceRetrievedUsingGetReference() throws Exception { + when(transactionMock.isActive()).thenReturn(true); + final OWLClassA reference = createReference(entityA.getUri()); + 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.uowChangeSet.hasChanges()); + } + + @Test + void removeRemovesInstanceRetrievedUsingGetReferenceFromRepository() { + when(transactionMock.isActive()).thenReturn(true); + final OWLClassA reference = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + assertTrue(uow.contains(result)); + uow.removeObject(result); + assertFalse(uow.contains(result)); + assertEquals(EntityState.REMOVED, uow.getState(result)); + verify(storageMock).remove(reference.getUri(), OWLClassA.class, descriptor); + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWorkGetReferenceTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWorkGetReferenceTest.java new file mode 100644 index 000000000..2324280b1 --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/OnCommitChangePropagatingUnitOfWorkGetReferenceTest.java @@ -0,0 +1,73 @@ +package cz.cvut.kbss.jopa.sessions; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.model.EntityState; +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.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.assertFalse; +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.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class OnCommitChangePropagatingUnitOfWorkGetReferenceTest extends AbstractUnitOfWorkGetReferenceTestRunner { + + @BeforeEach + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected AbstractUnitOfWork initUnitOfWork() { + return new OnCommitChangePropagatingUnitOfWork(serverSessionStub, new Configuration()); + } + + @Test + void removeOfInstanceRetrievedUsingGetReferenceSchedulesItForDeletion() { + final OWLClassA reference = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + assertTrue(uow.contains(result)); + uow.removeObject(result); + assertFalse(uow.contains(result)); + assertEquals(EntityState.REMOVED, uow.getState(result)); + } + + @Test + void removeOfInstanceRetrievedUsingGetReferenceRemovesItFromStorageOnCommit() { + when(transactionMock.isActive()).thenReturn(true); + final OWLClassA reference = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + uow.removeObject(result); + verify(storageMock, never()).remove(entityA.getUri(), OWLClassA.class, descriptor); + uow.commit(); + verify(storageMock).remove(entityA.getUri(), OWLClassA.class, descriptor); + } + + @Test + void changeOfAttributeValueOfInstanceRetrievedByGetReferenceIsPropagatedToStorageOnCommit() { + when(transactionMock.isActive()).thenReturn(true); + final OWLClassA reference = createReference(entityA.getUri()); + when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); + when(storageMock.find(any(LoadingParameters.class))).thenReturn(entityA); + final OWLClassA entity = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); + final String newStringValue = "New string attribute value"; + entity.setStringAttribute(newStringValue); + verify(storageMock, never()).merge(any(), eq(metamodelMocks.forOwlClassA().stringAttribute()), eq(descriptor)); + uow.commit(); + verify(storageMock).merge(any(), eq(metamodelMocks.forOwlClassA().stringAttribute()), eq(descriptor)); + } +} 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 deleted file mode 100644 index b02a4b8bf..000000000 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/UnitOfWorkGetReferenceTest.java +++ /dev/null @@ -1,283 +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.environment.OWLClassA; -import cz.cvut.kbss.jopa.environment.OWLClassD; -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.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.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); - final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); - assertNotNull(result); - assertSame(existing, result); - } - - @Test - void getReferenceReturnsExistingNewlyRegisteredObject() { - uow.registerNewObject(entityA, descriptor); - final OWLClassA result = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); - assertNotNull(result); - assertSame(entityA, result); - } - - @Test - void getReferenceReturnsNullWhenObjectIsScheduledForDeletion() { - final OWLClassA existing = (OWLClassA) uow.registerExistingObject(entityA, descriptor); - uow.removeObject(existing); - assertNull(uow.getReference(OWLClassA.class, entityA.getUri(), descriptor)); - } - - @Test - void getReferenceThrowsEntityExistsExceptionWhenIndividualIsAlreadyManagedAsDifferentIncompatibleType() { - uow.registerExistingObject(entityA, descriptor); - assertThrows(OWLEntityExistsException.class, - () -> uow.getReference(OWLClassD.class, entityA.getUri(), descriptor)); - } - - @Test - 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); - assertNotNull(result); - assertEquals(reference.getUri(), result.getUri()); - verify(storageMock).getReference(new LoadingParameters<>(OWLClassA.class, entityA.getUri(), descriptor)); - } - - @Test - void containsReturnsTrueForInstanceRetrievedUsingGetReference() { - 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); - assertTrue(uow.contains(result)); - } - - @Test - 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(EntityState.MANAGED, uow.getState(result)); - assertEquals(EntityState.MANAGED, uow.getState(result, descriptor)); - } - - @Test - void removeOfInstanceRetrievedUsingGetReferenceSchedulesItForDeletion() { - 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); - assertTrue(uow.contains(result)); - uow.removeObject(result); - assertFalse(uow.contains(result)); - assertEquals(EntityState.REMOVED, uow.getState(result)); - verify(storageMock).remove(entityA.getUri(), OWLClassA.class, descriptor); - } - - @Test - void loadEntityFieldLoadsValueOfAttributeOfInstanceRetrievedUsingGetReference() throws Exception { - 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, metamodelMocks.forOwlClassL().setAttribute()); - verify(storageMock).loadFieldValue(result, metamodelMocks.forOwlClassL().setAttribute(), descriptor); - assertEquals(LoadState.LOADED, uow.isLoaded(result, OWLClassL.getSetField().getName())); - } - - @Test - void loadEntityFieldDoesNothingWhenLazilyLoadedAttributeOfInstanceRetrievedUsingGetReferenceIsAlreadyLoaded() - throws Exception { - 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, metamodelMocks.forOwlClassL().setAttribute()); - // Call it twice. Storage should be called only once - uow.loadEntityField(result, metamodelMocks.forOwlClassL().setAttribute()); - verify(storageMock).loadFieldValue(result, metamodelMocks.forOwlClassL().setAttribute(), descriptor); - assertEquals(LoadState.LOADED, uow.isLoaded(result, OWLClassL.getSetField().getName())); - } - - @Test - void attributeChangedPropagatesChangeOfInstanceRetrievedUsingGetReferenceIntoRepository() throws Exception { - when(transactionMock.isActive()).thenReturn(true); - 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); - uow.attributeChanged(result, OWLClassA.getStrAttField()); - verify(storageMock).merge(result, metamodelMocks.forOwlClassA().stringAttribute(), descriptor); - } - - @Test - void attributeChangedDoesNotRegisterChangeForInstanceRetrievedUsingGetReference() throws Exception { - when(transactionMock.isActive()).thenReturn(true); - 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); - uow.attributeChanged(result, OWLClassA.getStrAttField()); - assertFalse(uow.uowChangeSet.hasChanges()); - } - - @Test - void removeRemovesInstanceRetrievedUsingGetReferenceFromRepository() { - when(transactionMock.isActive()).thenReturn(true); - 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); - assertTrue(uow.contains(result)); - uow.removeObject(result); - assertFalse(uow.contains(result)); - verify(storageMock).remove(reference.getUri(), OWLClassA.class, descriptor); - } - - @Test - void removeCreatesRemoveChangeOnCommitForInstanceRetrievedUsingGetReference() { - when(transactionMock.isActive()).thenReturn(true); - 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); - uow.removeObject(result); - uow.commit(); - verify(serverSessionStub.getLiveObjectCache()).evict(OWLClassA.class, reference.getUri(), descriptor.getSingleContext().orElse(null)); - } - - @Test - void getReferenceLoadsOriginalFromSecondLevelCacheWhenPresent() { - 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); - assertEquals(entityA, uow.getOriginal(result)); - } - - @Test - void changesToGetReferenceResultAreMergedIntoOriginalInCache() { - when(transactionMock.isActive()).thenReturn(true); - 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); - final String strValue = "string value"; - a.setStringAttribute(strValue); - uow.commit(); - assertEquals(strValue, entityA.getStringAttribute()); - } - - @Test - void uowCommitEvictsInstanceRetrievedUsingGetReferenceFromCacheWhenItWasNotPresentThereOnRetrieval() { - when(transactionMock.isActive()).thenReturn(true); - 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(serverSessionStub.getLiveObjectCache()).evict(OWLClassA.class, entityA.getUri(), descriptor.getSingleContext().orElse(null)); - } - - @Test - void attributeChangeSetsChangeRecordToPreventCachingWhenNewValueWasRetrievedUsingGetReference() throws Exception { - when(transactionMock.isActive()).thenReturn(true); - final OWLClassA reference = new OWLClassA(entityA.getUri()); - final OWLClassD owner = new OWLClassD(Generators.createIndividualIdentifier()); - when(storageMock.find(any(LoadingParameters.class))).thenReturn(owner); - when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); - final OWLClassD changed = uow.readObject(OWLClassD.class, owner.getUri(), descriptor); - final OWLClassA ref = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); - changed.setOwlClassA(ref); - - uow.attributeChanged(changed, OWLClassD.getOwlClassAField()); - - final ObjectChangeSet changeSet = uow.uowChangeSet.getExistingObjectChanges(owner); - assertFalse(changeSet.getChanges().isEmpty()); - final Optional changeRecord = - changeSet.getChanges().stream().filter(chr -> chr.getNewValue().equals(ref)).findFirst(); - assertTrue(changeRecord.isPresent()); - assertTrue(changeRecord.get().doesPreventCaching()); - } - - @Test - void mergeDetachedMarksChangeRecordForAttributeWithGetReferenceResultAsPreventingCaching() { - when(transactionMock.isActive()).thenReturn(true); - final OWLClassA reference = new OWLClassA(entityA.getUri()); - when(storageMock.getReference(any(LoadingParameters.class))).thenReturn(reference); - final OWLClassA ref = uow.getReference(OWLClassA.class, entityA.getUri(), descriptor); - final OWLClassD owner = new OWLClassD(Generators.createIndividualIdentifier()); - final OWLClassD toMerge = new OWLClassD(owner.getUri()); - when(storageMock.find(any(LoadingParameters.class))).thenReturn(owner); - when(storageMock.contains(owner.getUri(), OWLClassD.class, descriptor)).thenReturn(true); - toMerge.setOwlClassA(ref); - - uow.mergeDetached(toMerge, descriptor); - - final ObjectChangeSet changeSet = uow.uowChangeSet.getExistingObjectChanges(owner); - assertFalse(changeSet.getChanges().isEmpty()); - final Optional changeRecord = - changeSet.getChanges().stream().filter(chr -> chr.getNewValue().equals(ref)).findFirst(); - assertTrue(changeRecord.isPresent()); - assertTrue(changeRecord.get().doesPreventCaching()); - } -} 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 2aa70cdf5..e88cf69c0 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 @@ -40,6 +40,7 @@ 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.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -130,4 +131,17 @@ void mergeReplacesNewValueInChangeRecordWhenItIsReadFromUoW() { assertEquals(loaded, target.getOwlClassA()); assertEquals(loaded, changeRecord.getNewValue()); } + + @Test + void mergeUsesValueWhenItIsAlreadyManagedByUoW() { + final OWLClassD target = new OWLClassD(Generators.createIndividualIdentifier()); + final OWLClassA value = Generators.generateOwlClassAInstance(); + when(uow.contains(value)).thenReturn(true); + target.setOwlClassA(value); + final ChangeRecord changeRecord = new ChangeRecord(refASpec, value); + + sut.mergeValue(target, changeRecord, descriptor); + assertSame(value, target.getOwlClassA()); + verify(uow, never()).readObject(OWLClassA.class, value.getUri(), descriptor); + } } diff --git a/jopa-integration-tests-jena/pom.xml b/jopa-integration-tests-jena/pom.xml index 8d059a483..9ea01355d 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 - 2.0.0 + 2.0.1 ../pom.xml 4.0.0 diff --git a/jopa-integration-tests-owlapi/pom.xml b/jopa-integration-tests-owlapi/pom.xml index e762433cd..e295167b3 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 - 2.0.0 + 2.0.1 ../pom.xml 4.0.0 diff --git a/jopa-integration-tests-rdf4j/pom.xml b/jopa-integration-tests-rdf4j/pom.xml index 986f1825a..4902b0e79 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 - 2.0.0 + 2.0.1 ../pom.xml jopa-integration-tests-rdf4j 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 b98ff219f..0ea708504 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,7 +20,6 @@ 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; @@ -31,11 +30,4 @@ 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/pom.xml b/jopa-integration-tests/pom.xml index b38733e3a..408d25056 100644 --- a/jopa-integration-tests/pom.xml +++ b/jopa-integration-tests/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.0.0 + 2.0.1 ../pom.xml 4.0.0 diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassO.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassO.java index cf253e749..3e57fe3b0 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassO.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassO.java @@ -28,9 +28,12 @@ public class OWLClassO implements HasUri { @Id private URI uri; - @OWLObjectProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes#hasE", cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER) + @OWLObjectProperty(iri = Vocabulary.P_O_SET_OF_E_ATTRIBUTE, cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER) private Set owlClassESet; + @OWLObjectProperty(iri = Vocabulary.P_O_SINGLE_E_ATTRIBUTE, cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private OWLClassE owlClassE; + public OWLClassO() { } @@ -55,11 +58,20 @@ public void setOwlClassESet(Set owlClassESet) { this.owlClassESet = owlClassESet; } + public OWLClassE getOwlClassE() { + return owlClassE; + } + + public void setOwlClassE(OWLClassE owlClassE) { + this.owlClassE = owlClassE; + } + @Override public String toString() { return "OWLClassO{" + "uri=" + uri + ", owlClassESet=" + owlClassESet + + ", owlClassE=" + owlClassE + '}'; } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithQueryAttr6.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithQueryAttr6.java index 5efba93f3..d7fb7ece6 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithQueryAttr6.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithQueryAttr6.java @@ -17,25 +17,31 @@ */ package cz.cvut.kbss.jopa.test; -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.Id; +import cz.cvut.kbss.jopa.model.annotations.OWLClass; +import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; +import cz.cvut.kbss.jopa.model.annotations.Sparql; import java.net.URI; -import java.util.Set; @OWLClass(iri = Vocabulary.C_OwlClassWithQueryAttr6) public class OWLClassWithQueryAttr6 implements HasUri { - private static final String QUERY = "SELECT ?pluralAttribute\n" + - "WHERE {?this <" + Vocabulary.P_HAS_SIMPLE_LIST + "> ?pluralAttribute}"; + private static final String QUERY = "SELECT ?d WHERE { " + + "?d a <" + Vocabulary.C_OWL_CLASS_D + "> ;" + + " <" + Vocabulary.P_HAS_OWL_CLASS_A + "> ?a . " + + "?this <" + Vocabulary.P_HAS_OWL_CLASS_A + "> ?a . }"; @Id private URI uri; - @OWLObjectProperty(iri = Vocabulary.P_HAS_SIMPLE_LIST, cascade = CascadeType.ALL) - private Set pluralAttribute; + @OWLObjectProperty(iri = Vocabulary.P_HAS_OWL_CLASS_A, cascade = CascadeType.ALL) + private OWLClassA owlClassA; - @Sparql(query=QUERY, fetchType = FetchType.LAZY) - private Set pluralQueryAttribute; + @Sparql(query = QUERY, fetchType = FetchType.LAZY) + private OWLClassD lazyQueryAttribute; public OWLClassWithQueryAttr6() { } @@ -53,30 +59,26 @@ public URI getUri() { return uri; } - public Set getPluralAttribute() { - return pluralAttribute; + public OWLClassA getOwlClassA() { + return owlClassA; } - public void setPluralAttribute(Set pluralAttribute) { - this.pluralAttribute = pluralAttribute; + public void setOwlClassA(OWLClassA owlClassA) { + this.owlClassA = owlClassA; } - public Set getPluralQueryAttribute() { - return pluralQueryAttribute; + public OWLClassD getLazyQueryAttribute() { + return lazyQueryAttribute; } - public void setPluralQueryAttribute(Set pluralQueryAttribute) { - this.pluralQueryAttribute = pluralQueryAttribute; - } - - public static String getSparqlQuery() { - return QUERY; + public void setLazyQueryAttribute(OWLClassD lazyQueryAttribute) { + this.lazyQueryAttribute = lazyQueryAttribute; } @Override public String toString() { String out = "OWLClassWithQueryAttr: uri = " + uri; - out += ", pluralAttribute = " + pluralAttribute; + out += ", owlClassA = " + owlClassA; return out; } } 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 1043e020d..b7fa1214d 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 @@ -110,6 +110,9 @@ public class Vocabulary { public static final String P_N_URI_ANNOTATION_PROPERTY = ATTRIBUTE_IRI_BASE + "annotationUri"; public static final String P_N_STRING_ATTRIBUTE = ATTRIBUTE_IRI_BASE + "N-stringAttribute"; + public static final String P_O_SET_OF_E_ATTRIBUTE = ATTRIBUTE_IRI_BASE + "hasESet"; + public static final String P_O_SINGLE_E_ATTRIBUTE = ATTRIBUTE_IRI_BASE + "hasE"; + public static final String P_A_STRING_ATTRIBUTE = ATTRIBUTE_IRI_BASE + "A-stringAttribute"; public static final String P_B_STRING_ATTRIBUTE = ATTRIBUTE_IRI_BASE + "B-stringAttribute"; public static final String P_E_STRING_ATTRIBUTE = ATTRIBUTE_IRI_BASE + "E-stringAttribute"; 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 c38be057e..a4e70a226 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 @@ -155,31 +155,26 @@ public static Map> createTypedProperties(int size) { private static Object generateRandomPropertyValue(int valueIndex, int propertyIndex) { final int random = randomInt(10); - switch (random) { - case 0: // boolean - return valueIndex % 2 == 0; - case 1: // int - return valueIndex; - case 2: // long - return System.currentTimeMillis(); - case 3: //double - return ((double) propertyIndex + 1) / (valueIndex + 1); - case 4: // datetime + return switch (random) { + case 0 -> // boolean + valueIndex % 2 == 0; + case 1 -> // int + valueIndex; + case 2 -> // long + System.currentTimeMillis(); + case 3 -> //double + ((double) propertyIndex + 1) / (valueIndex + 1); + case 4 -> // datetime // Generate date rounded to milliseconds to prevent issues with time rounding - return OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS); - case 5: - return OffsetTime.now().truncatedTo(ChronoUnit.MILLIS); - case 6: - return LocalDate.now(); - case 7: // String - return "TypedProperty_" + propertyIndex + "Value_" + valueIndex; - case 8: - return BigInteger.valueOf(valueIndex); - case 9: - return BigDecimal.valueOf(Math.PI); - default: - throw new IllegalArgumentException(); - } + OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS); + case 5 -> OffsetTime.now().truncatedTo(ChronoUnit.MILLIS); + case 6 -> LocalDate.now(); + case 7 -> // String + "TypedProperty_" + propertyIndex + "Value_" + valueIndex; + case 8 -> BigInteger.valueOf(valueIndex); + case 9 -> BigDecimal.valueOf(Math.PI); + default -> throw new IllegalArgumentException(); + }; } private static void generateInstances(Collection col, String uriBase, int size) { @@ -195,6 +190,13 @@ private static void generateInstances(Collection col, String uriBase, } } + public static OWLClassA generateOwlClassA() { + final OWLClassA a = new OWLClassA(generateUri()); + a.setStringAttribute("String attribute " + randomInt(10000)); + a.setTypes(TYPES); + return a; + } + private static Set getTypes() { final Set types = new HashSet<>(3); types.add(Vocabulary.CLASS_IRI_BASE + "OWLClassDF"); 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 630770095..25607fcc3 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 @@ -23,10 +23,12 @@ 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.query.QueryHints; 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.query.QueryTestEnvironment; +import cz.cvut.kbss.ontodriver.Statement; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -211,6 +213,7 @@ public void askQueryAgainstTransactionalOntologyContainsUncommittedChangesAsWell "ASK { ?individual a ?type . }", Boolean.class).setParameter("individual", e.getUri()).setParameter("type", URI.create(Vocabulary.C_OWL_CLASS_E)); + query.setHint(QueryHints.TARGET_ONTOLOGY, Statement.StatementOntology.TRANSACTIONAL.toString()); final Boolean res = query.getSingleResult(); assertTrue(res); } finally { 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 6e225aa26..b720fd59a 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 @@ -46,7 +46,6 @@ 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; @@ -385,9 +384,7 @@ private static void replaceFileContents(File target) throws IOException { protected abstract void addFileStorageProperties(Map properties); @Test - @Disabled - void getReferenceRetrievesReferenceToInstanceWithDataPropertiesWhoseAttributesAreLoadedLazily() - throws Exception { + void getReferenceRetrievesReferenceToInstanceWithDataPropertiesWhoseAttributesAreLoadedLazily() throws Exception { this.em = getEntityManager( "getReferenceRetrievesReferenceToInstanceWithDataPropertiesWhoseAttributesAreLoadedLazily", false); persist(entityM); @@ -512,27 +509,24 @@ void testRetrieveEntityWithManagedTypeQueryAttr() { } @Test - @Disabled void testRetrieveWithLazyQueryAttribute() throws Exception { this.em = getEntityManager("RetrieveLazyQueryAttr", false); - Set simpleSet = Generators.createSimpleSet(20); - entityWithQueryAttr6.setPluralAttribute(simpleSet); + entityWithQueryAttr6.setOwlClassA(entityA); + entityD.setOwlClassA(entityA); + - persist(entityWithQueryAttr6); + persist(entityWithQueryAttr6, entityD); final OWLClassWithQueryAttr6 res = findRequired(OWLClassWithQueryAttr6.class, entityWithQueryAttr6.getUri()); - final Field f = OWLClassWithQueryAttr6.class.getDeclaredField("pluralQueryAttribute"); + final Field f = OWLClassWithQueryAttr6.class.getDeclaredField("lazyQueryAttribute"); f.setAccessible(true); Object value = f.get(res); assertInstanceOf(LazyLoadingProxy.class, value); - assertNotNull(res.getPluralQueryAttribute()); + assertNotNull(res.getLazyQueryAttribute()); value = f.get(res); assertNotNull(value); - assertEquals(entityWithQueryAttr6.getPluralAttribute(), res.getPluralQueryAttribute()); - for (OWLClassA classA : res.getPluralQueryAttribute()) { - assertTrue(em.contains(classA)); - } + assertEquals(entityD.getUri(), res.getLazyQueryAttribute().getUri()); } @Test 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 89c526350..8e63535ff 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 @@ -26,14 +26,17 @@ 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.OWLClassE; import cz.cvut.kbss.jopa.test.OWLClassF; import cz.cvut.kbss.jopa.test.OWLClassJ; +import cz.cvut.kbss.jopa.test.OWLClassO; 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.descriptor.AxiomValueDescriptor; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; @@ -62,6 +65,8 @@ 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.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -230,7 +235,9 @@ private void initOWLClassFAxioms(OWLClassF instance) throws Exception { 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(); + 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)); @@ -242,4 +249,33 @@ private void initOWLClassFAxioms(OWLClassF instance) throws Exception { setDesc.addAssertion(aSetAssertion); doReturn(aSetAxioms).when(connectionMock).find(setDesc); } + + /** + * Bug #248 + */ + @Test + void cascadeMergeOnLazyLoadingProxyDoesNothing() throws Exception { + final OWLClassO owner = new OWLClassO(Generators.generateUri()); + final OWLClassE reference = new OWLClassE(); + reference.setUri(Generators.generateUri()); + owner.setOwlClassE(reference); + final Assertion classAssertion = Assertion.createClassAssertion(false); + final Assertion eAssertion = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_O_SINGLE_E_ATTRIBUTE), false); + final Assertion eSetAssertion = Assertion.createObjectPropertyAssertion(URI.create(Vocabulary.P_O_SET_OF_E_ATTRIBUTE), false); + final NamedResource ownerSubject = NamedResource.create(owner.getUri()); + final AxiomDescriptor ownerDesc = new AxiomDescriptor(ownerSubject); + ownerDesc.addAssertion(classAssertion); + ownerDesc.addAssertion(eAssertion); + ownerDesc.addAssertion(eSetAssertion); + final List> ownerAxioms = List.of( + new AxiomImpl<>(NamedResource.create(owner.getUri()), classAssertion, new Value<>(URI.create(Vocabulary.C_OWL_CLASS_O))), + new AxiomImpl<>(NamedResource.create(owner.getUri()), eAssertion, new Value<>(NamedResource.create(reference.getUri()))) + ); + when(connectionMock.find(ownerDesc)).thenReturn(ownerAxioms); + + em.getTransaction().begin(); + final OWLClassO subject = em.find(OWLClassO.class, owner.getUri()); + em.merge(subject); + verify(connectionMock, never()).update(any(AxiomValueDescriptor.class)); + } } diff --git a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/EntityManagerTest.java b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/EntityManagerTest.java index 8b0095de4..3d53b7895 100644 --- a/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/EntityManagerTest.java +++ b/jopa-integration-tests/src/test/java/cz/cvut/kbss/jopa/test/integration/EntityManagerTest.java @@ -18,6 +18,7 @@ package cz.cvut.kbss.jopa.test.integration; import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; +import cz.cvut.kbss.jopa.test.OWLClassA; import cz.cvut.kbss.jopa.test.OWLClassF; import cz.cvut.kbss.jopa.test.Vocabulary; import cz.cvut.kbss.jopa.test.environment.Generators; @@ -30,6 +31,7 @@ import cz.cvut.kbss.ontodriver.model.Value; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.junit.jupiter.MockitoExtension; import java.net.URI; @@ -78,4 +80,17 @@ void mergeWithNullInferredValueIgnoresChangeWhenIgnoreInferredValueRemovalsOnMer verify(connectionMock, never()).update(any(AxiomValueDescriptor.class)); assertEquals(inferredValue, result.getSecondStringAttribute()); } + + @Test + void flushWritesChangesToRepository() throws Exception { + final OWLClassA entity = new OWLClassA(Generators.generateUri()); + entity.setStringAttribute("Test string"); + em.getTransaction().begin(); + em.persist(entity); + em.flush(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(AxiomValueDescriptor.class); + verify(connectionMock).persist(captor.capture()); + assertEquals(entity.getUri(), captor.getValue().getSubject().getIdentifier()); + em.getTransaction().commit(); + } } diff --git a/jopa-maven-plugin/pom.xml b/jopa-maven-plugin/pom.xml index 2f2267465..a600e75b2 100644 --- a/jopa-maven-plugin/pom.xml +++ b/jopa-maven-plugin/pom.xml @@ -5,7 +5,7 @@ jopa-all cz.cvut.kbss.jopa - 2.0.0 + 2.0.1 ../pom.xml 4.0.0 diff --git a/jopa-owl2java/pom.xml b/jopa-owl2java/pom.xml index adcbf84e6..7fc94244c 100644 --- a/jopa-owl2java/pom.xml +++ b/jopa-owl2java/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.0.0 + 2.0.1 ../pom.xml @@ -62,46 +62,24 @@ - - - release - - - - - com.google.code.maven-replacer-plugin - replacer - 1.5.3 - - - prepare-package - - replace - - - - - false - ${project.basedir}/src/main/java/cz/cvut/kbss/jopa/owl2java/Constants.java - false - $VERSION$ - ${project.parent.version} - - - - - - - src/main/sh true + + src/main/resources + true + + + org.apache.maven.plugins + maven-resources-plugin + ${maven.resources.plugin.version} + org.apache.maven.plugins maven-jar-plugin 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 66a13d2d8..4f679fdd6 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,8 +17,12 @@ */ package cz.cvut.kbss.jopa.owl2java; +import cz.cvut.kbss.jopa.owl2java.exception.OWL2JavaException; + +import java.io.IOException; import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.Properties; public class Constants { @@ -86,10 +90,21 @@ public class Constants { /** * Tool version. */ - public static final String VERSION = "$VERSION$"; + public static final String VERSION = resolveVersion(); private Constants() { throw new AssertionError(); } + + private static String resolveVersion() { + final Properties properties = new Properties(); + try { + properties.load(Constants.class.getClassLoader().getResourceAsStream("owl2java.properties")); + assert properties.containsKey("cz.cvut.jopa.owl2java.version"); + return properties.getProperty("cz.cvut.jopa.owl2java.version"); + } catch (IOException e) { + throw new OWL2JavaException("Unable to load OWL2Java version from properties file."); + } + } } diff --git a/jopa-owl2java/src/main/resources/owl2java.properties b/jopa-owl2java/src/main/resources/owl2java.properties new file mode 100644 index 000000000..ddf5e3f61 --- /dev/null +++ b/jopa-owl2java/src/main/resources/owl2java.properties @@ -0,0 +1 @@ +cz.cvut.jopa.owl2java.version=${project.version} diff --git a/jopa-owlapi-utils/pom.xml b/jopa-owlapi-utils/pom.xml index 120223ac4..915dbe47e 100644 --- a/jopa-owlapi-utils/pom.xml +++ b/jopa-owlapi-utils/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.0.0 + 2.0.1 ../pom.xml diff --git a/modelgen/pom.xml b/modelgen/pom.xml index 659187942..7934fe457 100644 --- a/modelgen/pom.xml +++ b/modelgen/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 2.0.0 + 2.0.1 ../pom.xml diff --git a/ontodriver-api/pom.xml b/ontodriver-api/pom.xml index e76d87cad..d05ba7032 100644 --- a/ontodriver-api/pom.xml +++ b/ontodriver-api/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 2.0.0 + 2.0.1 ../pom.xml 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 0f1f9b44c..4d85ae4aa 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 @@ -97,9 +97,15 @@ public int hashCode() { @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) && terminatedByNil == other.terminatedByNil; } 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 002eef180..adb02811e 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 @@ -65,11 +65,19 @@ 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-jena/pom.xml b/ontodriver-jena/pom.xml index c07fc74bb..38d8748ce 100644 --- a/ontodriver-jena/pom.xml +++ b/ontodriver-jena/pom.xml @@ -8,7 +8,7 @@ cz.cvut.kbss.jopa jopa-all - 2.0.0 + 2.0.1 ../pom.xml 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 f65e6a7ff..1fcf7d263 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 @@ -107,7 +107,7 @@ private Resource generateNewListNode(URI baseUri, String context, int index) { return node; } - private Optional createNilTerminal(Resource lastNode, Property hasNext, + private static Optional createNilTerminal(Resource lastNode, Property hasNext, ReferencedListDescriptor descriptor) { return descriptor.isTerminatedByNil() ? Optional.of(createStatement(lastNode, hasNext, RDF.nil)) : Optional.empty(); } diff --git a/ontodriver-owlapi/pom.xml b/ontodriver-owlapi/pom.xml index 9e71fcb2b..86e92bb2e 100644 --- a/ontodriver-owlapi/pom.xml +++ b/ontodriver-owlapi/pom.xml @@ -11,7 +11,7 @@ cz.cvut.kbss.jopa jopa-all - 2.0.0 + 2.0.1 ../pom.xml diff --git a/ontodriver-rdf4j/pom.xml b/ontodriver-rdf4j/pom.xml index 2953faf81..cc8909e78 100644 --- a/ontodriver-rdf4j/pom.xml +++ b/ontodriver-rdf4j/pom.xml @@ -10,11 +10,11 @@ jopa-all cz.cvut.kbss.jopa - 2.0.0 + 2.0.1 - 4.3.11 + 4.3.12 2.0.9 diff --git a/pom.xml b/pom.xml index 7071bfa72..468d3cc64 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 cz.cvut.kbss.jopa - 2.0.0 + 2.0.1 jopa-all pom JOPA @@ -37,6 +37,7 @@ 17 ${java.version} ${java.version} + yyyy-MM-dd 2.0.6 5.9.2 @@ -49,6 +50,7 @@ 3.1.1 3.6.3 3.2.5 + 3.3.1