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