Skip to content

Method: calculateChanges()

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.sessions;
19:
20: import cz.cvut.kbss.jopa.exceptions.EntityNotFoundException;
21: import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException;
22: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
23: import cz.cvut.kbss.jopa.sessions.cache.CacheManager;
24: import cz.cvut.kbss.jopa.model.EntityState;
25: import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties;
26: import cz.cvut.kbss.jopa.model.LoadState;
27: import cz.cvut.kbss.jopa.model.MetamodelImpl;
28: import cz.cvut.kbss.jopa.model.annotations.FetchType;
29: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
30: import cz.cvut.kbss.jopa.model.lifecycle.PostLoadInvoker;
31: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
32: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
33: import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType;
34: import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder;
35: import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy;
36: import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory;
37: import cz.cvut.kbss.jopa.sessions.cache.Descriptors;
38: import cz.cvut.kbss.jopa.sessions.change.Change;
39: import cz.cvut.kbss.jopa.sessions.change.ChangeCalculator;
40: import cz.cvut.kbss.jopa.sessions.change.ChangeRecord;
41: import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory;
42: import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet;
43: import cz.cvut.kbss.jopa.sessions.change.UnitOfWorkChangeSet;
44: import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor;
45: import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory;
46: import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration;
47: import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor;
48: import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry;
49: import cz.cvut.kbss.jopa.sessions.util.LoadingParameters;
50: import cz.cvut.kbss.jopa.sessions.validator.InferredAttributeChangeValidator;
51: import cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator;
52: import cz.cvut.kbss.jopa.utils.Configuration;
53: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
54: import cz.cvut.kbss.jopa.utils.IdentifierTransformer;
55: import cz.cvut.kbss.jopa.utils.MetamodelUtils;
56: import org.slf4j.Logger;
57: import org.slf4j.LoggerFactory;
58:
59: import java.lang.reflect.Field;
60: import java.net.URI;
61: import java.util.HashMap;
62: import java.util.IdentityHashMap;
63: import java.util.List;
64: import java.util.Map;
65: import java.util.Map.Entry;
66: import java.util.Objects;
67: import java.util.Set;
68:
69: import static cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException.individualAlreadyManaged;
70: import static cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator.getValidator;
71: import static cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator.isNotInferred;
72: import static cz.cvut.kbss.jopa.utils.EntityPropertiesUtils.getValueAsURI;
73:
74: public abstract class AbstractUnitOfWork extends AbstractSession implements UnitOfWork {
75:
76: private static final Logger LOG = LoggerFactory.getLogger(AbstractUnitOfWork.class);
77: final IndirectWrapperHelper indirectWrapperHelper;
78:
79: // Read-only!!! It is just the keyset of cloneToOriginals
80: final Set<Object> cloneMapping;
81: final Map<Object, Object> cloneToOriginals;
82: final Map<Object, Object> keysToClones = new HashMap<>();
83: final Map<Object, Object> deletedObjects;
84: final Map<Object, Object> newObjectsCloneToOriginal;
85: final Map<Object, Object> newObjectsKeyToClone = new HashMap<>();
86: private final Map<Object, Object> referenceProxies;
87: RepositoryMap repoMap;
88:
89: final LoadStateDescriptorRegistry loadStateRegistry;
90:
91: boolean hasChanges;
92: boolean hasNew;
93: boolean hasDeleted;
94:
95: private boolean transactionActive;
96: private boolean isActive;
97: private boolean flushingChanges;
98:
99: UnitOfWorkChangeSet uowChangeSet = ChangeSetFactory.createUoWChangeSet();
100:
101: final AbstractSession parent;
102: final ConnectionWrapper storage;
103:
104: final MergeManager mergeManager;
105: final CloneBuilder cloneBuilder;
106: final ChangeCalculator changeCalculator;
107: final SparqlQueryFactory queryFactory;
108: final InferredAttributeChangeValidator inferredAttributeChangeValidator;
109:
110: public AbstractUnitOfWork(AbstractSession parent, Configuration configuration) {
111: super(configuration);
112: this.parent = Objects.requireNonNull(parent);
113: this.cloneToOriginals = new IdentityHashMap<>();
114: this.cloneMapping = cloneToOriginals.keySet();
115: this.deletedObjects = new IdentityHashMap<>();
116: this.newObjectsCloneToOriginal = new IdentityHashMap<>();
117: this.referenceProxies = new IdentityHashMap<>();
118: this.repoMap = new RepositoryMap();
119: this.loadStateRegistry = new LoadStateDescriptorRegistry(this::stringify);
120: this.indirectWrapperHelper = new IndirectWrapperHelper(this);
121: this.cloneBuilder = new CloneBuilder(this);
122: this.storage = acquireConnection();
123: this.queryFactory = new SparqlQueryFactory(this, storage);
124: this.mergeManager = new MergeManager(this, cloneBuilder);
125: this.changeCalculator = new ChangeCalculator(this);
126: this.inferredAttributeChangeValidator = new InferredAttributeChangeValidator(storage);
127: this.isActive = true;
128: }
129:
130: @Override
131: protected ConnectionWrapper acquireConnection() {
132: final ConnectionWrapper conn = parent.acquireConnection();
133: conn.setUnitOfWork(this);
134: return conn;
135: }
136:
137: @Override
138: public void release() {
139: clear();
140: storage.close();
141: this.isActive = false;
142: LOG.debug("UnitOfWork released.");
143: }
144:
145: @Override
146: public void clear() {
147: detachAllManagedInstances();
148: cloneToOriginals.clear();
149: keysToClones.clear();
150: deletedObjects.clear();
151: newObjectsCloneToOriginal.clear();
152: newObjectsKeyToClone.clear();
153: loadStateRegistry.clear();
154: this.hasChanges = false;
155: this.hasDeleted = false;
156: this.hasNew = false;
157: cloneBuilder.reset();
158: this.repoMap = new RepositoryMap();
159: repoMap.initDescriptors();
160: this.uowChangeSet = ChangeSetFactory.createUoWChangeSet();
161: this.transactionActive = false;
162: }
163:
164: /**
165: * Detaches all managed entities from this persistence context.
166: */
167: abstract void detachAllManagedInstances();
168:
169: @Override
170: public CacheManager getLiveObjectCache() {
171: return parent.getLiveObjectCache();
172: }
173:
174: @Override
175: public boolean isActive() {
176: return isActive;
177: }
178:
179: @Override
180: public void begin() {
181: this.transactionActive = true;
182: }
183:
184: @Override
185: public void commit() {
186: LOG.trace("UnitOfWork commit started.");
187: if (!isActive()) {
188: throw new IllegalStateException("Cannot commit inactive Unit of Work!");
189: }
190: commitUnitOfWork();
191: LOG.trace("UnitOfWork commit finished.");
192: }
193:
194: /**
195: * Commit this Unit of Work.
196: */
197: private void commitUnitOfWork() {
198: this.flushingChanges = true;
199: commitToStorage();
200: mergeChangesIntoParent();
201: postCommit();
202: }
203:
204: void removeLazyLoadingProxies(Object entity) {
205: assert entity != null;
206: final EntityType<?> et = entityType(entity.getClass());
207: for (FieldSpecification<?, ?> fs : et.getFieldSpecifications()) {
208: final Object value = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), entity);
209: if (value instanceof LazyLoadingProxy<?> lazyLoadingProxy) {
210: EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, lazyLoadingProxy.unwrap());
211: }
212: }
213: }
214:
215: /**
216: * If there are any changes, commit them to the ontology.
217: */
218: abstract void commitToStorage();
219:
220: /**
221: * Merge the changes from this Unit of Work's change set into the server session.
222: */
223: private void mergeChangesIntoParent() {
224: if (hasChanges()) {
225: mergeManager.mergeChangesFromChangeSet(uowChangeSet);
226: }
227: evictPossiblyUpdatedReferencesFromCache();
228: }
229:
230: private void evictPossiblyUpdatedReferencesFromCache() {
231: cloneToOriginals.forEach((clone, orig) -> {
232: if (orig == null && !deletedObjects.containsKey(clone)) {
233: removeObjectFromCache(clone, getDescriptor(clone).getSingleContext().orElse(null));
234: }
235: });
236: }
237:
238: /**
239: * Cleans up after the commit.
240: */
241: private void postCommit() {
242: final boolean changes = hasChanges();
243: clear();
244: this.flushingChanges = false;
245: if (changes) {
246: getLiveObjectCache().evictInferredObjects();
247: }
248: }
249:
250: @Override
251: public void rollback() {
252: LOG.trace("UnitOfWork rollback started.");
253: if (!isActive()) {
254: throw new IllegalStateException("Cannot rollback inactive Unit of Work!");
255: }
256: storage.rollback();
257: clear();
258: }
259:
260: @Override
261: public boolean contains(Object entity) {
262: Objects.requireNonNull(entity);
263: return isObjectManaged(entity);
264: }
265:
266: @Override
267: public <T> T readObject(Class<T> cls, Object identifier, Descriptor descriptor) {
268: Objects.requireNonNull(cls);
269: Objects.requireNonNull(identifier);
270: Objects.requireNonNull(descriptor);
271:
272: return readObjectInternal(cls, identifier, descriptor);
273: }
274:
275: protected <T> T readObjectInternal(Class<T> cls, Object identifier, Descriptor descriptor) {
276: assert cls != null;
277: assert identifier != null;
278: assert descriptor != null;
279: T result = readManagedObject(cls, identifier, descriptor);
280: if (result != null) {
281: return result;
282: }
283: result = storage.find(new LoadingParameters<>(cls, getValueAsURI(identifier), descriptor));
284:
285: if (result == null) {
286: return null;
287: }
288: final Object clone = registerExistingObject(result, new CloneRegistrationDescriptor(descriptor).postCloneHandlers(List.of(new PostLoadInvoker(getMetamodel()))));
289: return cls.cast(clone);
290: }
291:
292: <T> T readManagedObject(Class<T> cls, Object identifier, Descriptor descriptor) {
293: // First try to find the object among new uncommitted objects
294: Object result = newObjectsKeyToClone.get(identifier);
295: if (result != null && (isInRepository(descriptor, result))) {
296: // The result can be returned, since it is already registered in this UOW
297: return cls.cast(result);
298: }
299: // Object is already managed
300: return getManagedClone(cls, identifier, descriptor);
301: }
302:
303: private boolean isInRepository(Descriptor descriptor, Object entity) {
304: assert descriptor != null;
305: assert entity != null;
306:
307: return repoMap.contains(descriptor, entity);
308: }
309:
310: private <T> T getManagedClone(Class<T> cls, Object identifier, Descriptor descriptor) {
311: if (!keysToClones.containsKey(identifier)) {
312: return null;
313: }
314: final Object clone = keysToClones.get(identifier);
315: if (!cls.isAssignableFrom(clone.getClass())) {
316: throw individualAlreadyManaged(identifier);
317: }
318: return isInRepository(descriptor, clone) && !deletedObjects.containsKey(clone) ? cls.cast(clone) : null;
319: }
320:
321: @Override
322: public <T> T getReference(Class<T> cls, Object identifier, Descriptor descriptor) {
323: Objects.requireNonNull(cls);
324: Objects.requireNonNull(identifier);
325: Objects.requireNonNull(descriptor);
326:
327: final T managed = readManagedObject(cls, identifier, descriptor);
328: if (managed != null) {
329: return managed;
330: }
331: if (keysToClones.containsKey(identifier)) {
332: throw new EntityNotFoundException("Entity '" + cls.getSimpleName() + "' with id " + IdentifierTransformer.stringifyIri(identifier) + " not found.");
333: }
334: final T reference = storage.getReference(new LoadingParameters<>(cls, getValueAsURI(identifier), descriptor));
335: registerEntityWithOntologyContext(reference, descriptor);
336: referenceProxies.put(reference, reference);
337: loadStateRegistry.put(reference, LoadStateDescriptorFactory.createNotLoaded(reference, entityType(cls)));
338: return reference;
339: }
340:
341: @Override
342: public <T> T readObjectWithoutRegistration(Class<T> cls, Object identifier, Descriptor descriptor) {
343: Objects.requireNonNull(cls);
344: Objects.requireNonNull(identifier);
345: Objects.requireNonNull(descriptor);
346:
347: T result = readManagedObject(cls, identifier, descriptor);
348: if (result != null) {
349: return result;
350: }
351: return storage.find(new LoadingParameters<>(cls, getValueAsURI(identifier), descriptor));
352: }
353:
354: @Override
355: public EntityState getState(Object entity) {
356: Objects.requireNonNull(entity);
357:
358: if (deletedObjects.containsKey(entity)) {
359: return EntityState.REMOVED;
360: } else if (newObjectsCloneToOriginal.containsKey(entity)) {
361: return EntityState.MANAGED_NEW;
362: } else if (cloneMapping.contains(entity) || referenceProxies.containsKey(entity)) {
363: return EntityState.MANAGED;
364: } else {
365: return EntityState.NOT_MANAGED;
366: }
367: }
368:
369: @Override
370: public EntityState getState(Object entity, Descriptor descriptor) {
371: Objects.requireNonNull(entity);
372: Objects.requireNonNull(descriptor);
373:
374: if (deletedObjects.containsKey(entity)) {
375: return EntityState.REMOVED;
376: } else if (newObjectsCloneToOriginal.containsKey(entity) && isInRepository(descriptor, entity)) {
377: return EntityState.MANAGED_NEW;
378: } else if ((cloneMapping.contains(entity) || referenceProxies.containsKey(entity)) && isInRepository(descriptor, entity)) {
379: return EntityState.MANAGED;
380: } else {
381: return EntityState.NOT_MANAGED;
382: }
383: }
384:
385: @Override
386: public boolean isObjectNew(Object entity) {
387: return entity != null && newObjectsCloneToOriginal.containsKey(entity);
388: }
389:
390: @Override
391: public boolean isObjectManaged(Object entity) {
392: Objects.requireNonNull(entity);
393:
394: return (cloneMapping.contains(entity) || isManagedReference(entity)) && !deletedObjects.containsKey(entity)
395: || newObjectsCloneToOriginal.containsKey(entity);
396: }
397:
398: private boolean isManagedReference(Object entity) {
399: return referenceProxies.containsKey(entity);
400: }
401:
402: @Override
403: public <T> T mergeDetached(T entity, Descriptor descriptor) {
404: Objects.requireNonNull(entity);
405: Objects.requireNonNull(descriptor);
406:
407: final Object id = getIdentifier(entity);
408: if (!storage.contains(id, entity.getClass(), descriptor)) {
409: registerNewObject(entity, descriptor);
410: return entity;
411: } else {
412: if (isIndividualManaged(id, entity) && !isSameType(id, entity)) {
413: throw individualAlreadyManaged(id);
414: }
415: return mergeDetachedInternal(entity, descriptor);
416: }
417: }
418:
419: Object getIdentifier(Object entity) {
420: return EntityPropertiesUtils.getIdentifier(entity, getMetamodel());
421: }
422:
423: private boolean isSameType(Object id, Object entity) {
424: final Class<?> mergedType = entity.getClass();
425: final Object managed = keysToClones.containsKey(id) ? keysToClones.get(id) : newObjectsKeyToClone.get(id);
426: assert managed != null;
427: final Class<?> managedType = MetamodelUtils.getEntityClass(managed.getClass());
428: return managedType.isAssignableFrom(mergedType);
429: }
430:
431: /**
432: * Merges the specified detached entity into this persistence context.
433: *
434: * @param entity Entity to merge
435: * @param descriptor Descriptor of the merged entity
436: * @param <T> Entity type
437: * @return Managed instance
438: */
439: abstract <T> T mergeDetachedInternal(T entity, Descriptor descriptor);
440:
441: @Override
442: public Object registerExistingObject(Object entity, Descriptor descriptor) {
443: return registerExistingObject(entity, new CloneRegistrationDescriptor(descriptor));
444: }
445:
446: @Override
447: public Object registerExistingObject(Object entity, CloneRegistrationDescriptor registrationDescriptor) {
448: if (entity == null) {
449: return null;
450: }
451: if (cloneToOriginals.containsValue(entity)) {
452: return getCloneForOriginal(entity);
453: }
454: final CloneConfiguration cloneConfig = CloneConfiguration.withDescriptor(registrationDescriptor.getDescriptor())
455: .forPersistenceContext(!isFlushingChanges())
456: .addPostRegisterHandlers(registrationDescriptor.getPostCloneHandlers());
457: Object clone = cloneBuilder.buildClone(entity, cloneConfig);
458: assert clone != null;
459: registerClone(clone, entity, registrationDescriptor.getDescriptor());
460: registrationDescriptor.getPostCloneHandlers().forEach(c -> c.accept(clone));
461: return clone;
462: }
463:
464: void registerClone(Object clone, Object original, Descriptor descriptor) {
465: cloneToOriginals.put(clone, original);
466: final Object identifier = EntityPropertiesUtils.getIdentifier(clone, getMetamodel());
467: keysToClones.put(identifier, clone);
468: registerEntityWithOntologyContext(clone, descriptor);
469: }
470:
471: protected <T> IdentifiableEntityType<T> entityType(Class<T> cls) {
472: return getMetamodel().entity(cls);
473: }
474:
475: /**
476: * This method calculates the changes that were to the registered entities and adds these changes into the given
477: * change set for future commit to the ontology.
478: */
479: void calculateChanges() {
480:• if (hasNew) {
481: calculateNewObjects(uowChangeSet);
482: }
483:• if (hasDeleted) {
484: calculateDeletedObjects(uowChangeSet);
485: }
486: }
487:
488: /**
489: * Create object change sets for the new objects and adds them into our UnitOfWorkChangeSet.
490: */
491: private void calculateNewObjects(UnitOfWorkChangeSet changeSet) {
492: for (Object clone : newObjectsCloneToOriginal.keySet()) {
493: final Descriptor c = getDescriptor(clone);
494: changeSet.addNewObjectChangeSet(ChangeSetFactory.createNewObjectChange(clone, c));
495: }
496: }
497:
498: private void calculateDeletedObjects(final UnitOfWorkChangeSet changeSet) {
499: for (Object clone : deletedObjects.keySet()) {
500: final Descriptor descriptor = getDescriptor(clone);
501: Object original = getOriginal(clone);
502: if (original == null) {
503: assert referenceProxies.containsKey(clone);
504: original = clone;
505: }
506: changeSet.addDeletedObjectChangeSet(ChangeSetFactory.createDeleteObjectChange(clone, original, descriptor));
507: changeSet.cancelObjectChanges(original);
508: }
509: }
510:
511: void persistNewObjects() {
512: if (hasNew) {
513: newObjectsKeyToClone.forEach((id, entity) -> {
514: final Descriptor descriptor = getDescriptor(entity);
515: storage.persist(id, entity, descriptor);
516: final IdentifiableEntityType<?> et = entityType(entity.getClass());
517: et.getLifecycleListenerManager().invokePostPersistCallbacks(entity);
518: });
519: }
520: }
521:
522: void validateIntegrityConstraints() {
523: final IntegrityConstraintsValidator validator = getValidator();
524: for (Change changeSet : uowChangeSet.getNewObjects()) {
525: validator.validate(changeSet.getClone(), entityType((Class<Object>) changeSet.getObjectClass()), isNotInferred());
526: }
527: uowChangeSet.getExistingObjectsChanges().forEach(changeSet -> validator.validate(changeSet, getMetamodel()));
528: }
529:
530: /**
531: * Tries to find the original object for the given clone. It searches the existing objects, new objects and deleted
532: * objects.
533: *
534: * @param clone Object
535: * @return The original object for the given clone
536: */
537: public Object getOriginal(Object clone) {
538: if (clone == null) {
539: return null;
540: }
541: return cloneToOriginals.containsKey(clone) ? cloneToOriginals.get(clone) : newObjectsCloneToOriginal.get(clone);
542: }
543:
544: /**
545: * Registers the specified original for the specified clone, assuming the clone is a new object.
546: * <p>
547: * This method must be called during commit when new objects are persisted so that they can be referenced by other
548: * objects.
549: *
550: * @param clone Already registered clone
551: * @param original Original to register
552: */
553: public void registerOriginalForNewClone(Object clone, Object original) {
554: assert flushingChanges;
555: assert newObjectsCloneToOriginal.containsKey(clone);
556: newObjectsCloneToOriginal.put(clone, original);
557: }
558:
559: /**
560: * Gets managed original with the specified identifier or {@code null} if there is none matching.
561: * <p>
562: * Descriptor is used to check repository context validity.
563: *
564: * @param cls Return type of the original
565: * @param identifier Instance identifier
566: * @param descriptor Repository descriptor
567: * @return Original object managed by this UoW or {@code null} if this UoW doesn't contain a matching instance
568: */
569: public <T> T getManagedOriginal(Class<T> cls, Object identifier, Descriptor descriptor) {
570: final T clone = getManagedClone(cls, identifier, descriptor);
571: return clone != null ? cls.cast(cloneToOriginals.get(clone)) : null;
572: }
573:
574: /**
575: * Check if this UnitOfWork contains this original entity. This method is used by the CloneBuilder so it does not
576: * have to clone already managed referenced objects.
577: *
578: * @param entity The original entity.
579: * @return True if the original is managed in this UnitOfWork.
580: */
581: boolean containsOriginal(Object entity) {
582: return entity != null && cloneToOriginals.containsValue(entity);
583: }
584:
585: /**
586: * Finds clone of the specified original.
587: *
588: * @param original The original object whose clone we are looking for
589: * @return The clone or null, if there is none
590: */
591: public Object getCloneForOriginal(Object original) {
592: for (Entry<Object, Object> entry : cloneToOriginals.entrySet()) {
593: // We use IdentityMap, so we can use ==
594: if (entry.getValue() == original) {
595: return entry.getKey();
596: }
597: }
598: return null;
599: }
600:
601: public boolean hasChanges() {
602: return hasChanges || hasDeleted || hasNew;
603: }
604:
605: void setHasChanges() {
606: this.hasChanges = true;
607: }
608:
609: void preventCachingIfReferenceIsNotLoaded(ChangeRecord changeRecord) {
610: final Object newValue = changeRecord.getNewValue();
611: if (newValue != null && contains(newValue) && isLoaded(newValue) != LoadState.LOADED) {
612: changeRecord.preventCaching();
613: }
614: }
615:
616: protected ObjectChangeSet processInferredValueChanges(ObjectChangeSet changeSet) {
617: if (getConfiguration().is(JOPAPersistenceProperties.IGNORE_INFERRED_VALUE_REMOVAL_ON_MERGE)) {
618: final ObjectChangeSet copy = ChangeSetFactory.createObjectChangeSet(changeSet.getOriginal(), changeSet.getClone(), changeSet.getDescriptor());
619: changeSet.getChanges().stream().filter(chr -> !(chr.getAttribute().isInferred() &&
620: inferredAttributeChangeValidator.isInferredValueRemoval(changeSet.getClone(), changeSet.getOriginal(),
621: (FieldSpecification) chr.getAttribute(),
622: changeSet.getDescriptor()))).forEach(copy::addChangeRecord);
623: return copy;
624: } else {
625: changeSet.getChanges().stream().filter(chr -> chr.getAttribute().isInferred()).forEach(
626: chr -> inferredAttributeChangeValidator.validateChange(changeSet.getClone(), changeSet.getOriginal(),
627: (FieldSpecification) chr.getAttribute(),
628: changeSet.getDescriptor()));
629: return changeSet;
630: }
631: }
632:
633: protected <T> T getInstanceForMerge(URI identifier, EntityType<T> et, Descriptor descriptor) {
634: if (keysToClones.containsKey(identifier)) {
635: T managed = (T) keysToClones.get(identifier);
636: et.getFieldSpecifications().stream().filter(fs -> fs.getFetchType() == FetchType.LAZY).forEach(fs -> {
637: final Object fieldValue = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), managed);
638: if (fieldValue instanceof LazyLoadingProxy<?> proxy) {
639: proxy.triggerLazyLoading();
640: }
641: });
642: return managed;
643: }
644: final LoadingParameters<T> params = new LoadingParameters<>(et.getJavaType(), identifier, descriptor, true);
645: T original = storage.find(params);
646: assert original != null;
647:
648: return (T) registerExistingObject(original, new CloneRegistrationDescriptor(descriptor).allEager(true));
649: }
650:
651: protected void evictAfterMerge(EntityType<?> et, URI identifier, Descriptor descriptor) {
652: if (getLiveObjectCache().contains(et.getJavaType(), identifier, descriptor)) {
653: getLiveObjectCache().evict(et.getJavaType(), identifier, descriptor.getSingleContext().orElse(null));
654: }
655: getMetamodel().getReferringTypes(et.getJavaType()).forEach(getLiveObjectCache()::evict);
656: }
657:
658: protected static ObjectChangeSet copyChangeSet(ObjectChangeSet changeSet, Object original, Object clone,
659: Descriptor descriptor) {
660: final ObjectChangeSet newChangeSet = ChangeSetFactory.createObjectChangeSet(original, clone, descriptor);
661: changeSet.getChanges().forEach(newChangeSet::addChangeRecord);
662: return newChangeSet;
663: }
664:
665: @Override
666: public <T> void refreshObject(T object) {
667: Objects.requireNonNull(object);
668: ensureManaged(object);
669:
670: final IdentifiableEntityType<T> et = entityType((Class<T>) object.getClass());
671: final URI idUri = EntityPropertiesUtils.getIdentifier(object, et);
672: final Descriptor descriptor = getDescriptor(object);
673:
674: final LoadingParameters<T> params = new LoadingParameters<>(et.getJavaType(), idUri, descriptor, true);
675: params.bypassCache();
676: final ConnectionWrapper connection = acquireConnection();
677: try {
678: uowChangeSet.cancelObjectChanges(getOriginal(object));
679: T original = connection.find(params);
680: if (original == null) {
681: throw new EntityNotFoundException("Entity " + stringify(object) + " no longer exists in the repository.");
682: }
683: removeLazyLoadingProxies(object);
684: T source = (T) cloneBuilder.buildClone(original, CloneConfiguration.withDescriptor(descriptor));
685: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(source, object, descriptor);
686: changeCalculator.calculateChanges(chSet);
687: new RefreshInstanceMerger(indirectWrapperHelper).mergeChanges(chSet);
688: revertTransactionalChanges(object, descriptor, chSet);
689: registerClone(object, original, descriptor);
690: loadStateRegistry.put(object, LoadStateDescriptorFactory.createAllLoaded(object, et));
691: et.getLifecycleListenerManager().invokePostLoadCallbacks(object);
692: } finally {
693: connection.close();
694: }
695: }
696:
697: private <T> void revertTransactionalChanges(T object, Descriptor descriptor, ObjectChangeSet chSet) {
698: for (ChangeRecord change : chSet.getChanges()) {
699: storage.merge(object, (FieldSpecification<? super T, ?>) change.getAttribute(), descriptor.getAttributeDescriptor(change.getAttribute()));
700: }
701: }
702:
703: @Override
704: public void registerNewObject(Object entity, Descriptor descriptor) {
705: Objects.requireNonNull(entity);
706: Objects.requireNonNull(descriptor);
707:
708: final IdentifiableEntityType<?> eType = entityType(entity.getClass());
709: eType.getLifecycleListenerManager().invokePrePersistCallbacks(entity);
710: Object id = initEntityIdentifier(entity, (EntityType<Object>) eType);
711: assert id != null;
712: verifyCanPersist(id, entity, eType, descriptor);
713: // Original is null until commit
714: newObjectsCloneToOriginal.put(entity, null);
715: registerEntityWithOntologyContext(entity, descriptor);
716: loadStateRegistry.put(entity, LoadStateDescriptorFactory.createAllLoaded(entity, (EntityType<Object>) eType));
717: newObjectsKeyToClone.put(id, entity);
718: this.hasNew = true;
719: }
720:
721: private Object initEntityIdentifier(Object entity, EntityType<Object> et) {
722: Object id = getIdentifier(entity);
723: if (id == null) {
724: EntityPropertiesUtils.verifyIdentifierIsGenerated(entity, et);
725: id = storage.generateIdentifier(et);
726: EntityPropertiesUtils.setIdentifier(id, entity, et);
727: }
728: return id;
729: }
730:
731: private void verifyCanPersist(Object id, Object instance, EntityType<?> et, Descriptor descriptor) {
732: if (isIndividualManaged(id, instance) && !instance.getClass().isEnum()) {
733: throw individualAlreadyManaged(id);
734: }
735: if (storage.contains(id, instance.getClass(), descriptor)) {
736: throw new OWLEntityExistsException("Individual " + id + " of type " + et.getIRI() + " already exists in storage.");
737: }
738: }
739:
740: private boolean isIndividualManaged(Object identifier, Object entity) {
741: return keysToClones.containsKey(identifier) || newObjectsKeyToClone.containsKey(identifier) && !cloneMapping.contains(entity);
742: }
743:
744:
745: <T> void ensureManaged(T object) {
746: if (!isObjectManaged(object)) {
747: throw new IllegalArgumentException("Object not managed by this persistence context.");
748: }
749: }
750:
751: @Override
752: public void restoreRemovedObject(Object entity) {
753: assert deletedObjects.containsKey(entity);
754:
755: deletedObjects.remove(entity);
756: final Object id = getIdentifier(entity);
757: storage.persist(id, entity, getDescriptor(entity));
758: }
759:
760: @Override
761: public void unregisterObject(Object object) {
762: if (object == null) {
763: return;
764: }
765: final Object original = cloneToOriginals.remove(object);
766: keysToClones.remove(EntityPropertiesUtils.getIdentifier(object, getMetamodel()));
767:
768: deletedObjects.remove(object);
769: if (hasNew) {
770: newObjectsCloneToOriginal.remove(object);
771: }
772: if (original != null) {
773: cloneBuilder.removeVisited(original, repoMap.getEntityDescriptor(object));
774: }
775: unregisterEntityFromOntologyContext(object);
776: }
777:
778: private void unregisterEntityFromOntologyContext(Object entity) {
779: assert entity != null;
780:
781: final Descriptor descriptor = repoMap.getEntityDescriptor(entity);
782: if (descriptor == null) {
783: throw new OWLPersistenceException("Fatal error, unable to find descriptor for entity " + stringify(entity));
784: }
785: repoMap.remove(descriptor, entity);
786: repoMap.removeEntityToRepository(entity);
787: }
788:
789: @Override
790: public void writeUncommittedChanges() {
791: if (hasChanges()) {
792: commitUnitOfWork();
793: }
794: }
795:
796: @Override
797: public MetamodelImpl getMetamodel() {
798: return parent.getMetamodel();
799: }
800:
801: @Override
802: public boolean isEntityType(Class<?> cls) {
803: return getMetamodel().isEntityType(cls);
804: }
805:
806: @Override
807: public boolean isInTransaction() {
808: return transactionActive;
809: }
810:
811: @Override
812: public boolean isFlushingChanges() {
813: return flushingChanges;
814: }
815:
816: @Override
817: public <T> Object loadEntityField(T entity, FieldSpecification<? super T, ?> fieldSpec) {
818: Objects.requireNonNull(entity);
819: Objects.requireNonNull(fieldSpec);
820: final Field field = fieldSpec.getJavaField();
821: assert field.getDeclaringClass().isAssignableFrom(entity.getClass());
822:
823: final Descriptor entityDescriptor = getDescriptor(entity);
824: final LoadStateDescriptor<?> loadStateDescriptor = loadStateRegistry.get(entity);
825: if (loadStateDescriptor.isLoaded(fieldSpec) == LoadState.LOADED) {
826: return EntityPropertiesUtils.getFieldValue(field, entity);
827: }
828:
829: storage.loadFieldValue(entity, fieldSpec, entityDescriptor);
830: final Object orig = EntityPropertiesUtils.getFieldValue(field, entity);
831: final Object entityOriginal = getOriginal(entity);
832: if (entityOriginal != null) {
833: EntityPropertiesUtils.setFieldValue(field, entityOriginal, orig);
834: }
835: final Descriptor fieldDescriptor = getFieldDescriptor(entity, field, entityDescriptor);
836: final Object clone = cloneLoadedFieldValue(entity, field, fieldDescriptor, orig);
837: EntityPropertiesUtils.setFieldValue(field, entity, clone);
838: loadStateDescriptor.setLoaded((FieldSpecification) fieldSpec, LoadState.LOADED);
839: return clone;
840: }
841:
842: /**
843: * Gets basic object info for logging.
844: * <p>
845: * This works around using {@link Object#toString()} for entities, which could inadvertently trigger lazy field
846: * fetching and cause an infinite field loading loop.
847: *
848: * @param object Object to stringify
849: * @return String info about the specified object
850: */
851: public String stringify(Object object) {
852: assert object != null;
853: return isEntityType(object.getClass()) ?
854: (object.getClass().getSimpleName() + IdentifierTransformer.stringifyIri(
855: EntityPropertiesUtils.getIdentifier(object, getMetamodel()))) :
856: object.toString();
857: }
858:
859: private <T> Descriptor getFieldDescriptor(T entity, Field field, Descriptor entityDescriptor) {
860: final EntityType<?> et = entityType(entity.getClass());
861: final FieldSpecification<?, ?> fieldSpec = et.getFieldSpecification(field.getName());
862: return entityDescriptor.getAttributeDescriptor(fieldSpec);
863: }
864:
865: private <T> Object cloneLoadedFieldValue(T entity, Field field, final Descriptor fieldDescriptor,
866: final Object fieldValueOrig) {
867: Object clone;
868: if (fieldValueOrig == null) {
869: clone = null;
870: } else {
871: if (isEntityType(field.getType())) {
872: clone = registerExistingObject(fieldValueOrig, fieldDescriptor);
873: putObjectIntoCache(getIdentifier(clone), fieldValueOrig, fieldDescriptor);
874: } else {
875: clone = cloneBuilder.buildClone(entity, field, fieldValueOrig, fieldDescriptor);
876: }
877: }
878: return clone;
879: }
880:
881: @Override
882: public void putObjectIntoCache(Object identifier, Object entity, Descriptor descriptor) {
883: final LoadStateDescriptor<?> loadStateDescriptor = loadStateRegistry.get(entity);
884: assert loadStateDescriptor != null;
885: getLiveObjectCache().add(identifier, entity, new Descriptors(descriptor, loadStateDescriptor));
886: }
887:
888: @Override
889: public void removeObjectFromCache(Object toRemove, URI context) {
890: Objects.requireNonNull(toRemove);
891:
892: getLiveObjectCache().evict(MetamodelUtils.getEntityClass(toRemove.getClass()), getIdentifier(toRemove), context);
893: }
894:
895: @Override
896: public boolean isConsistent(URI context) {
897: return storage.isConsistent(context);
898: }
899:
900: @Override
901: public List<URI> getContexts() {
902: return storage.getContexts();
903: }
904:
905: @Override
906: public <T> boolean isInferred(T entity, FieldSpecification<? super T, ?> attribute, Object value) {
907: Objects.requireNonNull(entity);
908: Objects.requireNonNull(attribute);
909: Objects.requireNonNull(value);
910: ensureManaged(entity);
911:
912: final Descriptor descriptor = getDescriptor(entity);
913: assert loadStateRegistry.contains(entity);
914: if (loadStateRegistry.get(entity).isLoaded(attribute) == LoadState.NOT_LOADED) {
915: value = loadEntityField(entity, attribute);
916: }
917: return storage.isInferred(entity, attribute, value, descriptor);
918: }
919:
920: @Override
921: public LoadState isLoaded(Object entity, String attributeName) {
922: Objects.requireNonNull(entity);
923: final FieldSpecification<?, ?> fs = entityType(entity.getClass()).getFieldSpecification(attributeName);
924: return loadStateRegistry.contains(entity) ? loadStateRegistry.get(entity).isLoaded(fs) : LoadState.UNKNOWN;
925: }
926:
927: @Override
928: public LoadState isLoaded(Object entity) {
929: Objects.requireNonNull(entity);
930: return loadStateRegistry.contains(entity) ? loadStateRegistry.get(entity).isLoaded() : LoadState.UNKNOWN;
931: }
932:
933: public SparqlQueryFactory sparqlQueryFactory() {
934: return queryFactory;
935: }
936:
937: public CriteriaBuilder getCriteriaBuilder() {
938: return parent.getCriteriaBuilder();
939: }
940:
941: void registerEntityWithOntologyContext(Object entity, Descriptor descriptor) {
942: assert descriptor != null;
943: assert entity != null;
944:
945: repoMap.add(descriptor, entity, null);
946: repoMap.addEntityToRepository(entity, descriptor);
947: }
948:
949: Descriptor getDescriptor(Object entity) {
950: assert entity != null;
951:
952: final Descriptor descriptor = repoMap.getEntityDescriptor(entity);
953: if (descriptor == null) {
954: throw new OWLPersistenceException("Unable to find descriptor of entity " + stringify(entity) + " in this UoW!");
955: }
956: return descriptor;
957: }
958:
959: public LoadStateDescriptorRegistry getLoadStateRegistry() {
960: return loadStateRegistry;
961: }
962:
963: @Override
964: public <T> T unwrap(Class<T> cls) {
965: if (cls.isAssignableFrom(getClass())) {
966: return cls.cast(this);
967: }
968: return storage.unwrap(cls);
969: }
970:
971: protected void markCloneForDeletion(Object entity, Object identifier) {
972: if (hasNew && newObjectsCloneToOriginal.containsKey(entity)) {
973: unregisterObject(entity);
974: newObjectsKeyToClone.remove(identifier);
975: } else {
976: deletedObjects.put(entity, entity);
977: this.hasDeleted = true;
978: }
979: }
980: }