Skip to content

Method: checkForUnpersistedChanges()

1: /**
2: * Copyright (C) 2020 Czech Technical University in Prague
3: *
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: *
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.oom;
16:
17: import cz.cvut.kbss.jopa.exceptions.StorageAccessException;
18: import cz.cvut.kbss.jopa.model.MetamodelImpl;
19: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
20: import cz.cvut.kbss.jopa.model.metamodel.Attribute;
21: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
22: import cz.cvut.kbss.jopa.model.metamodel.EntityTypeImpl;
23: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
24: import cz.cvut.kbss.jopa.oom.exceptions.EntityDeconstructionException;
25: import cz.cvut.kbss.jopa.oom.exceptions.EntityReconstructionException;
26: import cz.cvut.kbss.jopa.oom.exceptions.UnpersistedChangeException;
27: import cz.cvut.kbss.jopa.sessions.CacheManager;
28: import cz.cvut.kbss.jopa.sessions.LoadingParameters;
29: import cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl;
30: import cz.cvut.kbss.jopa.utils.Configuration;
31: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
32: import cz.cvut.kbss.ontodriver.Connection;
33: import cz.cvut.kbss.ontodriver.descriptor.*;
34: import cz.cvut.kbss.ontodriver.exception.OntoDriverException;
35: import cz.cvut.kbss.ontodriver.model.*;
36: import org.slf4j.Logger;
37: import org.slf4j.LoggerFactory;
38:
39: import java.lang.reflect.Field;
40: import java.net.URI;
41: import java.util.Collection;
42: import java.util.List;
43: import java.util.Objects;
44: import java.util.Set;
45:
46: import static cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException.individualAlreadyManaged;
47:
48: public class ObjectOntologyMapperImpl implements ObjectOntologyMapper, EntityMappingHelper {
49:
50: private static final Logger LOG = LoggerFactory.getLogger(ObjectOntologyMapperImpl.class);
51:
52: private final UnitOfWorkImpl uow;
53: private final CacheManager cache;
54: private final Connection storageConnection;
55: private final MetamodelImpl metamodel;
56:
57: private final AxiomDescriptorFactory descriptorFactory;
58: private final EntityConstructor entityBuilder;
59: private final EntityDeconstructor entityBreaker;
60: private final InstanceRegistry instanceRegistry;
61: private final PendingReferenceRegistry pendingReferences;
62:
63: private final EntityInstanceLoader defaultInstanceLoader;
64: private final EntityInstanceLoader twoStepInstanceLoader;
65:
66: public ObjectOntologyMapperImpl(UnitOfWorkImpl uow, Connection connection) {
67: this.uow = Objects.requireNonNull(uow);
68: this.cache = uow.getLiveObjectCache();
69: this.storageConnection = Objects.requireNonNull(connection);
70: this.metamodel = uow.getMetamodel();
71: this.descriptorFactory = new AxiomDescriptorFactory();
72: this.instanceRegistry = new InstanceRegistry();
73: this.pendingReferences = new PendingReferenceRegistry();
74: this.entityBuilder = new EntityConstructor(this);
75: this.entityBreaker = new EntityDeconstructor(this);
76:
77: this.defaultInstanceLoader = DefaultInstanceLoader.builder().connection(storageConnection).metamodel(metamodel)
78: .descriptorFactory(descriptorFactory)
79: .entityBuilder(entityBuilder).cache(cache).build();
80: this.twoStepInstanceLoader = TwoStepInstanceLoader.builder().connection(storageConnection).metamodel(metamodel)
81: .descriptorFactory(descriptorFactory)
82: .entityBuilder(entityBuilder).cache(cache).build();
83: }
84:
85: @Override
86: public <T> boolean containsEntity(Class<T> cls, URI identifier, Descriptor descriptor) {
87: assert cls != null;
88: assert identifier != null;
89: assert descriptor != null;
90:
91: final EntityType<T> et = getEntityType(cls);
92: final NamedResource classUri = NamedResource.create(et.getIRI().toURI());
93: final Axiom<NamedResource> ax = new AxiomImpl<>(NamedResource.create(identifier),
94: Assertion.createClassAssertion(false), new Value<>(classUri));
95: try {
96: return storageConnection.contains(ax, descriptor.getContext());
97: } catch (OntoDriverException e) {
98: throw new StorageAccessException(e);
99: }
100: }
101:
102: @Override
103: public <T> T loadEntity(LoadingParameters<T> loadingParameters) {
104: assert loadingParameters != null;
105:
106: instanceRegistry.reset();
107: return loadEntityInternal(loadingParameters);
108: }
109:
110: private <T> T loadEntityInternal(LoadingParameters<T> loadingParameters) {
111: final EntityTypeImpl<T> et = getEntityType(loadingParameters.getEntityType());
112: final T result;
113: if (et.hasSubtypes()) {
114: result = twoStepInstanceLoader.loadEntity(loadingParameters);
115: } else {
116: result = defaultInstanceLoader.loadEntity(loadingParameters);
117: }
118: if (result != null) {
119: cache.add(loadingParameters.getIdentifier(), result, loadingParameters.getDescriptor());
120: }
121: return result;
122: }
123:
124: @Override
125: public <T> T loadReference(LoadingParameters<T> loadingParameters) {
126: assert loadingParameters != null;
127:
128: final EntityTypeImpl<T> et = getEntityType(loadingParameters.getEntityType());
129: if (et.hasSubtypes()) {
130: return twoStepInstanceLoader.loadReference(loadingParameters);
131: } else {
132: return defaultInstanceLoader.loadReference(loadingParameters);
133: }
134: }
135:
136: @Override
137: public <T> EntityTypeImpl<T> getEntityType(Class<T> cls) {
138: return metamodel.entity(cls);
139: }
140:
141: @Override
142: public <T> void loadFieldValue(T entity, Field field, Descriptor descriptor) {
143: assert entity != null;
144: assert field != null;
145: assert descriptor != null;
146:
147: LOG.trace("Lazily loading value of field {} of entity {}.", field, entity);
148:
149: final EntityType<T> et = (EntityType<T>) getEntityType(entity.getClass());
150: final URI primaryKey = EntityPropertiesUtils.getIdentifier(entity, et);
151:
152: final AxiomDescriptor axiomDescriptor = descriptorFactory.createForFieldLoading(primaryKey,
153: field, descriptor, et);
154: try {
155: final Collection<Axiom<?>> axioms = storageConnection.find(axiomDescriptor);
156: entityBuilder.setFieldValue(entity, field, axioms, et, descriptor);
157: } catch (OntoDriverException e) {
158: throw new StorageAccessException(e);
159: } catch (IllegalArgumentException | IllegalAccessException e) {
160: throw new EntityReconstructionException(e);
161: }
162: }
163:
164: @Override
165: public <T> void persistEntity(URI identifier, T entity, Descriptor descriptor) {
166: assert entity != null;
167: assert descriptor != null;
168:
169: @SuppressWarnings("unchecked") final EntityType<T> et = (EntityType<T>) getEntityType(entity.getClass());
170: try {
171: if (identifier == null) {
172: identifier = generateIdentifier(et);
173: assert identifier != null;
174: EntityPropertiesUtils.setIdentifier(identifier, entity, et);
175: }
176: entityBreaker.setReferenceSavingResolver(new ReferenceSavingResolver(this));
177: final AxiomValueGatherer axiomBuilder = entityBreaker.mapEntityToAxioms(identifier, entity, et, descriptor);
178: axiomBuilder.persist(storageConnection);
179: persistPendingReferences(entity, axiomBuilder.getSubjectIdentifier());
180: } catch (IllegalArgumentException e) {
181: throw new EntityDeconstructionException("Unable to deconstruct entity " + entity, e);
182: }
183: }
184:
185: @Override
186: public URI generateIdentifier(EntityType<?> et) {
187: try {
188: return storageConnection.generateIdentifier(et.getIRI().toURI());
189: } catch (OntoDriverException e) {
190: throw new StorageAccessException(e);
191: }
192: }
193:
194: private <T> void persistPendingReferences(T instance, NamedResource identifier) {
195: try {
196: final Set<PendingAssertion> pas = pendingReferences.removeAndGetPendingAssertionsWith(instance);
197: for (PendingAssertion pa : pas) {
198: final AxiomValueDescriptor desc = new AxiomValueDescriptor(pa.getOwner());
199: desc.addAssertionValue(pa.getAssertion(), new Value<>(identifier));
200: desc.setAssertionContext(pa.getAssertion(), pa.getContext());
201: storageConnection.persist(desc);
202: }
203: final Set<PendingReferenceRegistry.PendingListReference> pLists =
204: pendingReferences.removeAndGetPendingListReferencesWith(instance);
205: final EntityType<?> et = getEntityType(instance.getClass());
206: for (PendingReferenceRegistry.PendingListReference list : pLists) {
207: final ListValueDescriptor desc = list.getDescriptor();
208: ListPropertyStrategy.addItemsToDescriptor(desc, list.getValues(), et);
209: if (desc instanceof SimpleListValueDescriptor) {
210: // TODO This can be an update or a persist
211: storageConnection.lists().updateSimpleList((SimpleListValueDescriptor) desc);
212: } else {
213: storageConnection.lists().updateReferencedList((ReferencedListValueDescriptor) desc);
214: }
215: }
216: } catch (OntoDriverException e) {
217: throw new StorageAccessException(e);
218: }
219: }
220:
221: @Override
222: public <T> T getEntityFromCacheOrOntology(Class<T> cls, URI identifier, Descriptor descriptor) {
223: final T orig = uow.getManagedOriginal(cls, identifier, descriptor);
224: if (orig != null) {
225: return orig;
226: }
227: if (cache.contains(cls, identifier, descriptor)) {
228: return cache.get(cls, identifier, descriptor);
229: } else if (instanceRegistry.containsInstance(identifier, descriptor.getContext())) {
230: final Object existing = instanceRegistry.getInstance(identifier, descriptor.getContext());
231: if (!cls.isAssignableFrom(existing.getClass())) {
232: throw individualAlreadyManaged(identifier);
233: }
234: // This prevents endless cycles in bidirectional relationships
235: return cls.cast(existing);
236: } else {
237: return loadEntityInternal(new LoadingParameters<>(cls, identifier, descriptor));
238: }
239: }
240:
241: @Override
242: public <T> T getOriginalInstance(T clone) {
243: assert clone != null;
244: return (T) uow.getOriginal(clone);
245: }
246:
247: boolean isManaged(Object instance) {
248: return uow.isObjectManaged(instance);
249: }
250:
251: <T> void registerInstance(URI primaryKey, T instance, URI context) {
252: instanceRegistry.registerInstance(primaryKey, instance, context);
253: }
254:
255: @Override
256: public void checkForUnpersistedChanges() {
257:• if (pendingReferences.hasPendingResources()) {
258: throw new UnpersistedChangeException(
259: "The following instances were neither persisted nor marked as cascade for persist: "
260: + pendingReferences.getPendingResources());
261: }
262: }
263:
264: void registerPendingAssertion(NamedResource owner, Assertion assertion, Object object, URI context) {
265: pendingReferences.addPendingAssertion(owner, assertion, object, context);
266: }
267:
268: void registerPendingListReference(Object item, ListValueDescriptor listDescriptor, List<?> values) {
269: pendingReferences.addPendingListReference(item, listDescriptor, values);
270: }
271:
272: @Override
273: public <T> void removeEntity(URI identifier, Class<T> cls, Descriptor descriptor) {
274: final EntityType<T> et = getEntityType(cls);
275: final AxiomDescriptor axiomDescriptor = descriptorFactory.createForEntityLoading(
276: new LoadingParameters<>(cls, identifier, descriptor, true), et);
277: try {
278: storageConnection.remove(axiomDescriptor);
279: pendingReferences.removePendingReferences(axiomDescriptor.getSubject());
280: } catch (OntoDriverException e) {
281: throw new StorageAccessException("Exception caught when removing entity.", e);
282: }
283: }
284:
285: @Override
286: public <T> void updateFieldValue(T entity, Field field, Descriptor entityDescriptor) {
287: @SuppressWarnings("unchecked") final EntityType<T> et = (EntityType<T>) getEntityType(entity.getClass());
288: final URI pkUri = EntityPropertiesUtils.getIdentifier(entity, et);
289:
290: entityBreaker.setReferenceSavingResolver(new ReferenceSavingResolver(this));
291: // It is OK to do it like this, because if necessary, the mapping will re-register a pending assertion
292: removePendingAssertions(et, field, pkUri);
293: final AxiomValueGatherer axiomBuilder = entityBreaker
294: .mapFieldToAxioms(pkUri, entity, field, et, entityDescriptor);
295: axiomBuilder.update(storageConnection);
296: }
297:
298: private <T> void removePendingAssertions(EntityType<T> et, Field field, URI identifier) {
299: final FieldSpecification<? super T, ?> fs = et.getFieldSpecification(field.getName());
300: if (fs instanceof Attribute) {
301: final Attribute<?, ?> att = (Attribute<?, ?>) fs;
302: // We care only about object property assertions, others are never pending
303: final Assertion assertion = Assertion.createObjectPropertyAssertion(att.getIRI().toURI(), att.isInferred());
304: pendingReferences.removePendingReferences(NamedResource.create(identifier), assertion);
305: }
306: }
307:
308: @Override
309: public Collection<Axiom<NamedResource>> loadSimpleList(SimpleListDescriptor listDescriptor) {
310: try {
311: return storageConnection.lists().loadSimpleList(listDescriptor);
312: } catch (OntoDriverException e) {
313: throw new StorageAccessException(e);
314: }
315: }
316:
317: @Override
318: public Collection<Axiom<NamedResource>> loadReferencedList(ReferencedListDescriptor listDescriptor) {
319: try {
320: return storageConnection.lists().loadReferencedList(listDescriptor);
321: } catch (OntoDriverException e) {
322: throw new StorageAccessException(e);
323: }
324: }
325:
326: @Override
327: public Configuration getConfiguration() {
328: return uow.getConfiguration();
329: }
330: }