diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/PersistenceUnitUtil.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/PersistenceUnitUtil.java index 64aff75c2..51cda429c 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/PersistenceUnitUtil.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/PersistenceUnitUtil.java @@ -1,35 +1,55 @@ /** * Copyright (C) 2016 Czech Technical University in Prague - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * This program 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 General Public License for more - * details. You should have received a copy of the GNU General Public License - * along with this program. If not, see . + *

+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later + * version. + *

+ * This program 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 General Public License for more + * details. You should have received a copy of the GNU General Public License along with this program. If not, see + * . */ package cz.cvut.kbss.jopa.model; +/** + * Utility interface between the application and the persistence provider managing the persistence unit. + *

+ * The methods of this interface should only be invoked on entity instances obtained from or managed by entity managers + * for this persistence unit or on new entity instances. + */ public interface PersistenceUnitUtil { - public boolean isLoaded(Object entity, String attributeName); + /** + * Determine the load state of a given persistent attribute of an entity belonging to the persistence unit. + * + * @param entity entity instance containing the attribute + * @param attributeName name of attribute whose load state is to be determined + * @return false if entity's state has not been loaded or if the attribute state has not been loaded, else true + */ + boolean isLoaded(Object entity, String attributeName); - public boolean isLoaded(Object entity); + /** + * Determine the load state of an entity belonging to the persistence unit. + *

+ * This method can be used to determine the load state of an entity passed as a reference. An entity is considered + * loaded if all attributes for which {@code FetchType.EAGER} has been specified have been loaded. + *

+ * The {@link #isLoaded(Object, String)} method should be used to determine the load state of an attribute. Not + * doing so might lead to unintended loading of state. + * + * @param entity entity instance whose load state is to be determined + * @return false if the entity has not been loaded, else true + */ + boolean isLoaded(Object entity); - /** - * Return the id of the entity. A generated id is not guaranteed to be - * available until after the database insert has occurred. Returns null if - * the entity does not yet have an id. - * - * @param entity - * entity instance - * @return id of the entity - * @throws IllegalArgumentException - * if the object is found not to be an entity - */ - public Object getIdentifier(Object entity); + /** + * Return the id of the entity. A generated id is not guaranteed to be available until after the database insert has + * occurred. Returns null if the entity does not yet have an id. + * + * @param entity entity instance + * @return id of the entity + * @throws IllegalArgumentException if the object is found not to be an entity + */ + Object getIdentifier(Object entity); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImpl.java index a9145b462..5a6d48acf 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImpl.java @@ -1,16 +1,14 @@ /** * Copyright (C) 2016 Czech Technical University in Prague - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * This program 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 General Public License for more - * details. You should have received a copy of the GNU General Public License - * along with this program. If not, see . + *

+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later + * version. + *

+ * This program 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 General Public License for more + * details. You should have received a copy of the GNU General Public License along with this program. If not, see + * . */ package cz.cvut.kbss.jopa.model; @@ -25,10 +23,7 @@ import cz.cvut.kbss.ontodriver.OntologyStorageProperties; import cz.cvut.kbss.ontodriver.config.OntoDriverProperties; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class EntityManagerFactoryImpl implements EntityManagerFactory, PersistenceUnitUtil { @@ -160,16 +155,18 @@ public void addNamedQuery(String name, Query query) { @Override public Object getIdentifier(Object entity) { + Objects.requireNonNull(entity); final EntityType et = getMetamodel().entity(entity.getClass()); return EntityPropertiesUtils.getFieldValue(et.getIdentifier().getJavaField(), entity); } @Override public boolean isLoaded(Object entity, String attributeName) { + Objects.requireNonNull(entity); + Objects.requireNonNull(attributeName); for (final AbstractEntityManager emi : em) { - if (emi.contains(entity)) { - return attributeName == null || emi.isLoaded(entity, attributeName); - + if (emi.contains(entity) && emi.isLoaded(entity, attributeName)) { + return true; } } @@ -178,9 +175,14 @@ public boolean isLoaded(Object entity, String attributeName) { @Override public boolean isLoaded(Object entity) { - // TODO + Objects.requireNonNull(entity); + // Since we do not support getReference yet, all EAGER attributes are always loaded for managed instances + for (AbstractEntityManager emi : em) { + if (emi.contains(entity)) { + return true; + } + } return false; - // return isLoaded(entity); } @Override 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 d8deb30d6..5e35fcf09 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 @@ -1,25 +1,25 @@ /** * Copyright (C) 2016 Czech Technical University in Prague - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * This program 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 General Public License for more - * details. You should have received a copy of the GNU General Public License - * along with this program. If not, see . + *

+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later + * version. + *

+ * This program 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 General Public License for more + * details. You should have received a copy of the GNU General Public License along with this program. If not, see + * . */ package cz.cvut.kbss.jopa.model; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; import cz.cvut.kbss.jopa.exceptions.TransactionRequiredException; import cz.cvut.kbss.jopa.model.annotations.CascadeType; +import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.model.metamodel.Metamodel; import cz.cvut.kbss.jopa.model.query.Query; import cz.cvut.kbss.jopa.model.query.TypedQuery; @@ -409,8 +409,17 @@ public Metamodel getMetamodel() { @Override public boolean isLoaded(final Object object, final String attributeName) { - // TODO - return false; + Objects.requireNonNull(object); + Objects.requireNonNull(attributeName); + if (!contains(object)) { + return false; + } + final FieldSpecification fieldSpec = getMetamodel().entity(object.getClass()) + .getFieldSpecification(attributeName); + // This is not correct, as lazily loaded fields can be set to null, but as long as we do not have any representation + // of the loaded state of an entity, this will have to do + return fieldSpec.getFetchType() == FetchType.EAGER || + EntityPropertiesUtils.getFieldValue(fieldSpec.getJavaField(), object) != null; } @Override 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 f72cbe240..2158f1c15 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 @@ -62,6 +62,7 @@ public static void initOWLClassAMocks(EntityTypeImpl etMock, Attribut when(strAttMock.getPersistentAttributeType()).thenReturn(Attribute.PersistentAttributeType.DATA); when(strAttMock.getConstraints()).thenReturn(new ParticipationConstraint[]{}); when(strAttMock.getCascadeTypes()).thenReturn(new CascadeType[0]); + when(strAttMock.getFetchType()).thenReturn(FetchType.EAGER); when(typesMock.getJavaField()).thenReturn(OWLClassA.getTypesField()); when(typesMock.getName()).thenReturn(OWLClassA.getTypesField().getName()); when(typesMock.getDeclaringType()).thenReturn(etMock); @@ -387,6 +388,9 @@ public static void initOWLClassKMocks(EntityTypeImpl etMock, Attribut when(clsEMock.getIRI()).thenReturn(IRI.create(clsEIri)); when(clsEMock.getJavaType()).thenReturn(OWLClassE.class); when(clsEMock.getDeclaringType()).thenReturn(etMock); + when(clsEMock.getFetchType()).thenReturn(FetchType.LAZY); + when(clsEMock.getName()).thenReturn(OWLClassK.getOwlClassEField().getName()); + when(etMock.getFieldSpecification(clsEMock.getName())).thenReturn(clsEMock); when(etMock.getIdentifier()).thenReturn(idMock); when(idMock.getJavaField()).thenReturn(OWLClassK.class.getDeclaredField("uri")); when(etMock.getLifecycleListenerManager()).thenReturn(EntityLifecycleListenerManager.empty()); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImplTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImplTest.java new file mode 100644 index 000000000..658a91a7e --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerFactoryImplTest.java @@ -0,0 +1,89 @@ +package cz.cvut.kbss.jopa.model; + +import cz.cvut.kbss.jopa.environment.OWLClassA; +import cz.cvut.kbss.jopa.environment.utils.DataSourceStub; +import cz.cvut.kbss.jopa.environment.utils.Generators; +import cz.cvut.kbss.ontodriver.Connection; +import cz.cvut.kbss.ontodriver.Types; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class EntityManagerFactoryImplTest { + + private EntityManagerFactoryImpl emf; + + @Mock + private Connection connection; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + final Map props = new HashMap<>(); + props.put(JOPAPersistenceProperties.DATA_SOURCE_CLASS, DataSourceStub.class.getName()); + props.put(JOPAPersistenceProperties.ONTOLOGY_PHYSICAL_URI_KEY, + Generators.createIndividualIdentifier().toString()); + props.put(JOPAPersistenceProperties.SCAN_PACKAGE, "cz.cvut.kbss.jopa.environment"); + this.emf = new EntityManagerFactoryImpl(props); + emf.createEntityManager(); + emf.getServerSession().unwrap(DataSourceStub.class).setConnection(connection); + when(connection.types()).thenReturn(mock(Types.class)); + } + + @Test + public void isLoadedReturnsTrueForManagedInstance() { + final EntityManager em = emf.createEntityManager(); + try { + final OWLClassA a = Generators.generateOwlClassAInstance(); + em.persist(a); + assertTrue(emf.isLoaded(a)); + } finally { + em.close(); + } + } + + @Test + public void isLoadedReturnsTrueForAttributeOfManagedInstance() throws Exception { + final EntityManager em = emf.createEntityManager(); + try { + final OWLClassA a = Generators.generateOwlClassAInstance(); + em.persist(a); + assertTrue(emf.isLoaded(a, OWLClassA.getStrAttField().getName())); + } finally { + em.close(); + } + } + + @Test + public void isLoadedReturnsFalseNonNonManagedInstance() { + final EntityManager emOne = emf.createEntityManager(); + final EntityManager emTwo = emf.createEntityManager(); + try { + assertFalse(emf.isLoaded(Generators.generateOwlClassAInstance())); + } finally { + emOne.close(); + emTwo.close(); + } + } + + @Test + public void isLoadedReturnsFalseNonNonManagedInstanceWithAttribute() throws Exception { + final EntityManager emOne = emf.createEntityManager(); + final EntityManager emTwo = emf.createEntityManager(); + try { + assertFalse(emf.isLoaded(Generators.generateOwlClassAInstance(), OWLClassA.getStrAttField().getName())); + } finally { + emOne.close(); + emTwo.close(); + } + } +} \ No newline at end of file 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 31116f8ba..395883491 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 @@ -481,4 +481,31 @@ public void removeIsAbleToBreakCascadingCycle() throws Exception { verify(uow).removeObject(cloneOne); verify(uow).removeObject(cloneTwo); } + + @Test + public void isLoadedReturnsTrueForEagerlyLoadedAttributeOfManagedInstance() throws Exception { + final OWLClassA a = Generators.generateOwlClassAInstance(); + doAnswer((invocationOnMock) -> a).when(uow).readObject(eq(OWLClassA.class), eq(a.getUri()), any(Descriptor.class)); + when(uow.contains(a)).thenReturn(true); + final OWLClassA found = em.find(OWLClassA.class, a.getUri()); + assertTrue(em.isLoaded(found, OWLClassA.getStrAttField().getName())); + } + + @Test + public void isLoadedReturnsFalseForNonManagedInstance() throws Exception { + final OWLClassA a = Generators.generateOwlClassAInstance(); + when(uow.contains(a)).thenReturn(false); + assertFalse(em.isLoaded(a, OWLClassA.getStrAttField().getName())); + } + + @Test + public void isLoadedReturnsTrueForNonNullLazilyLoadedAttribute() throws Exception { + final OWLClassK inst = new OWLClassK(); + inst.setUri(Generators.createIndividualIdentifier()); + inst.setOwlClassE(new OWLClassE()); + when(uow.contains(inst)).thenReturn(true); + doAnswer((invocationOnMock) -> inst).when(uow).readObject(eq(OWLClassK.class), eq(inst.getUri()), any(Descriptor.class)); + final OWLClassK found = em.find(OWLClassK.class, inst.getUri()); + assertTrue(em.isLoaded(found, OWLClassK.getOwlClassEField().getName())); + } }