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