Skip to contentMethod: registerNewObjectInternal(Object, Descriptor)
1: /**
2: * Copyright (C) 2022 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.sessions;
16:
17: import cz.cvut.kbss.jopa.adapters.IndirectCollection;
18: import cz.cvut.kbss.jopa.adapters.IndirectWrapper;
19: import cz.cvut.kbss.jopa.exceptions.EntityNotFoundException;
20: import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException;
21: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
22: import cz.cvut.kbss.jopa.model.AbstractEntityManager;
23: import cz.cvut.kbss.jopa.model.BeanListenerAspect;
24: import cz.cvut.kbss.jopa.model.EntityManagerImpl.State;
25: import cz.cvut.kbss.jopa.model.LoadState;
26: import cz.cvut.kbss.jopa.model.MetamodelImpl;
27: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
28: import cz.cvut.kbss.jopa.model.lifecycle.PostLoadInvoker;
29: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
30: import cz.cvut.kbss.jopa.model.metamodel.EntityTypeImpl;
31: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
32: import cz.cvut.kbss.jopa.query.NamedQueryManager;
33: import cz.cvut.kbss.jopa.query.ResultSetMappingManager;
34: import cz.cvut.kbss.jopa.query.criteria.CriteriaBuilderImpl;
35: import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory;
36: import cz.cvut.kbss.jopa.sessions.change.ChangeManagerImpl;
37: import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl;
38: import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory;
39: import cz.cvut.kbss.jopa.sessions.descriptor.InstanceDescriptor;
40: import cz.cvut.kbss.jopa.sessions.descriptor.InstanceDescriptorFactory;
41: import cz.cvut.kbss.jopa.sessions.validator.AttributeModificationValidator;
42: import cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator;
43: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
44: import cz.cvut.kbss.jopa.utils.Wrapper;
45: import org.aspectj.lang.Aspects;
46:
47: import java.lang.reflect.Field;
48: import java.net.URI;
49: import java.util.*;
50: import java.util.Map.Entry;
51: import java.util.function.Consumer;
52:
53: import static cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException.individualAlreadyManaged;
54: import static cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator.getValidator;
55: import static cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator.isNotInferred;
56: import static cz.cvut.kbss.jopa.utils.EntityPropertiesUtils.getValueAsURI;
57:
58: public class UnitOfWorkImpl extends AbstractSession implements UnitOfWork, ConfigurationHolder, Wrapper {
59:
60: // Read-only!!! It is just the keyset of cloneToOriginals
61: private final Set<Object> cloneMapping;
62: private final Map<Object, Object> cloneToOriginals;
63: private final Map<Object, Object> keysToClones = new HashMap<>();
64: private final Map<Object, Object> deletedObjects;
65: private final Map<Object, Object> newObjectsCloneToOriginal;
66: private final Map<Object, Object> newObjectsKeyToClone = new HashMap<>();
67: private final Map<Object, InstanceDescriptor> instanceDescriptors;
68: private RepositoryMap repoMap;
69:
70: private boolean hasChanges;
71: private boolean hasNew;
72: private boolean hasDeleted;
73: private boolean shouldReleaseAfterCommit;
74: private boolean shouldClearCacheAfterCommit;
75: private boolean useTransactionalOntology;
76:
77: private boolean isActive;
78: private boolean inCommit;
79:
80: private UnitOfWorkChangeSet uowChangeSet = ChangeSetFactory.createUoWChangeSet();
81:
82: private final AbstractSession parent;
83: private AbstractEntityManager entityManager;
84: private final ConnectionWrapper storage;
85:
86: private final MergeManager mergeManager;
87: private final CloneBuilder cloneBuilder;
88: private final ChangeManager changeManager;
89: private final SparqlQueryFactory queryFactory;
90: private final CriteriaBuilder criteriaFactory;
91: private final IndirectWrapperHelper indirectWrapperHelper;
92: /**
93: * This is a shortcut for the second level cache.
94: */
95: private final CacheManager cacheManager;
96:
97: public UnitOfWorkImpl(AbstractSession parent) {
98: super(parent.getConfiguration());
99: this.parent = Objects.requireNonNull(parent);
100: this.cloneToOriginals = createMap();
101: this.cloneMapping = cloneToOriginals.keySet();
102: this.deletedObjects = createMap();
103: this.newObjectsCloneToOriginal = createMap();
104: this.instanceDescriptors = new IdentityHashMap<>();
105: this.repoMap = new RepositoryMap();
106: this.cloneBuilder = new CloneBuilderImpl(this);
107: this.indirectWrapperHelper = new IndirectWrapperHelper(this);
108: this.cacheManager = parent.getLiveObjectCache();
109: this.storage = acquireConnection();
110: this.queryFactory = new SparqlQueryFactory(this, storage);
111: this.criteriaFactory = new CriteriaBuilderImpl(this);
112: this.mergeManager = new MergeManagerImpl(this);
113: this.changeManager = new ChangeManagerImpl(this);
114: this.useTransactionalOntology = true;
115: this.isActive = true;
116: }
117:
118: CloneBuilder getCloneBuilder() {
119: return cloneBuilder;
120: }
121:
122: /**
123: * This method returns null, since we don't support nested Units of Work yet.
124: */
125: @Override
126: public UnitOfWork acquireUnitOfWork() {
127: return null;
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 <T> T readObject(Class<T> cls, Object identifier, Descriptor descriptor) {
139: Objects.requireNonNull(cls);
140: Objects.requireNonNull(identifier);
141: Objects.requireNonNull(descriptor);
142:
143: return readObjectInternal(cls, identifier, descriptor);
144: }
145:
146: private <T> T readObjectInternal(Class<T> cls, Object identifier, Descriptor descriptor) {
147: assert cls != null;
148: assert identifier != null;
149: assert descriptor != null;
150: T result = readManagedObject(cls, identifier, descriptor);
151: if (result != null) {
152: return result;
153: }
154: result = storage.find(new LoadingParameters<>(cls, getValueAsURI(identifier), descriptor));
155:
156: if (result == null) {
157: return null;
158: }
159: final Object clone = registerExistingObject(result, descriptor,
160: Collections.singletonList(new PostLoadInvoker(getMetamodel())));
161: checkForIndirectObjects(clone);
162: return cls.cast(clone);
163: }
164:
165: private <T> T readManagedObject(Class<T> cls, Object identifier, Descriptor descriptor) {
166: // First try to find the object among new uncommitted objects
167: Object result = newObjectsKeyToClone.get(identifier);
168: if (result != null && (isInRepository(descriptor, result))) {
169: // The result can be returned, since it is already registered in this UOW
170: return cls.cast(result);
171: }
172: // Object is already managed
173: return getManagedClone(cls, identifier, descriptor);
174: }
175:
176: private <T> T getManagedClone(Class<T> cls, Object identifier, Descriptor descriptor) {
177: if (!keysToClones.containsKey(identifier)) {
178: return null;
179: }
180: final Object clone = keysToClones.get(identifier);
181: if (!cls.isAssignableFrom(clone.getClass())) {
182: throw individualAlreadyManaged(identifier);
183: }
184: return isInRepository(descriptor, clone) && !deletedObjects.containsKey(clone) ? cls.cast(clone) : null;
185: }
186:
187: @Override
188: public <T> T getReference(Class<T> cls, Object identifier, Descriptor descriptor) {
189: Objects.requireNonNull(cls);
190: Objects.requireNonNull(identifier);
191: Objects.requireNonNull(descriptor);
192:
193: final T managedResult = readManagedObject(cls, identifier, descriptor);
194: if (managedResult != null) {
195: return managedResult;
196: }
197: final T result = storage.getReference(new LoadingParameters<>(cls, getValueAsURI(identifier), descriptor));
198: if (result == null) {
199: return null;
200: }
201: instanceDescriptors.put(result, InstanceDescriptorFactory.createNotLoaded(result, entityType(cls)));
202: registerEntityWithPersistenceContext(result);
203: registerEntityWithOntologyContext(result, descriptor);
204: if (getLiveObjectCache().contains(cls, identifier, descriptor)) {
205: cloneToOriginals.put(result, getLiveObjectCache().get(cls, identifier, descriptor));
206: } else {
207: cloneToOriginals.put(result, null);
208: }
209: keysToClones.put(identifier, result);
210: return result;
211: }
212:
213: /**
214: * This method calculates the changes that were to the registered entities and adds these changes into the given
215: * change set for future commit to the ontology.
216: */
217: private void calculateChanges() {
218: if (hasNew) {
219: calculateNewObjects(uowChangeSet);
220: }
221: if (hasDeleted) {
222: calculateDeletedObjects(uowChangeSet);
223: }
224: }
225:
226: /**
227: * Create object change sets for the new objects and adds them into our UnitOfWorkChangeSet.
228: *
229: * @param changeSet UnitOfWorkChangeSet
230: */
231: private void calculateNewObjects(UnitOfWorkChangeSet changeSet) {
232: for (Object clone : newObjectsCloneToOriginal.keySet()) {
233: final Descriptor c = getDescriptor(clone);
234: Object original = newObjectsCloneToOriginal
235: .computeIfAbsent(clone, key -> cloneBuilder.buildClone(key, new CloneConfiguration(c)));
236: if (original == null) {
237: throw new OWLPersistenceException(
238: "Error while calculating changes for new objects. Original not found.");
239: }
240: newObjectsCloneToOriginal.put(clone, original);
241: changeSet.addNewObjectChangeSet(ChangeSetFactory.createObjectChangeSet(original, clone,
242: c));
243: }
244: }
245:
246: private void calculateDeletedObjects(final UnitOfWorkChangeSet changeSet) {
247: for (Object clone : deletedObjects.keySet()) {
248: Descriptor descriptor = getDescriptor(clone);
249: changeSet.addDeletedObjectChangeSet(ChangeSetFactory.createDeleteObjectChangeSet(clone, descriptor));
250: changeSet.cancelObjectChanges(getOriginal(clone));
251: }
252: }
253:
254: @Override
255: public void clear() {
256: detachAllManagedInstances();
257: cloneToOriginals.clear();
258: keysToClones.clear();
259: deletedObjects.clear();
260: newObjectsCloneToOriginal.clear();
261: newObjectsKeyToClone.clear();
262: instanceDescriptors.clear();
263: this.hasChanges = false;
264: this.hasDeleted = false;
265: this.hasNew = false;
266: cloneBuilder.reset();
267: this.repoMap = new RepositoryMap();
268: repoMap.initDescriptors();
269: this.uowChangeSet = ChangeSetFactory.createUoWChangeSet();
270: }
271:
272: private void detachAllManagedInstances() {
273: cloneMapping.forEach(instance -> {
274: removeIndirectCollections(instance);
275: deregisterEntityFromPersistenceContext(instance);
276: });
277: newObjectsCloneToOriginal.keySet().forEach(instance -> {
278: removeIndirectCollections(instance);
279: deregisterEntityFromPersistenceContext(instance);
280: });
281: }
282:
283: @Override
284: public boolean contains(Object entity) {
285: Objects.requireNonNull(entity);
286: return isObjectManaged(entity);
287: }
288:
289: @Override
290: public void commit() {
291: LOG.trace("UnitOfWork commit started.");
292: if (!isActive()) {
293: throw new IllegalStateException("Cannot commit inactive Unit of Work!");
294: }
295: this.inCommit = true;
296: commitUnitOfWork();
297: LOG.trace("UnitOfWork commit finished.");
298: }
299:
300: @Override
301: public void rollback() {
302: LOG.trace("UnitOfWork rollback started.");
303: if (!isActive()) {
304: throw new IllegalStateException("Cannot rollback inactive Unit of Work!");
305: }
306: storage.rollback();
307: clear();
308: }
309:
310: /**
311: * Commit this Unit of Work.
312: */
313: private void commitUnitOfWork() {
314: commitToOntology();
315: mergeChangesIntoParent();
316: postCommit();
317: }
318:
319: /**
320: * Clean up after the commit.
321: */
322: private void postCommit() {
323: final boolean changes = hasChanges();
324: clear();
325: this.inCommit = false;
326: if (changes) {
327: if (shouldClearCacheAfterCommit) {
328: cacheManager.evictAll();
329: this.shouldReleaseAfterCommit = true;
330: } else {
331: cacheManager.evictInferredObjects();
332: }
333: }
334:
335: }
336:
337: /**
338: * If there are any changes, commit them to the ontology.
339: */
340: private void commitToOntology() {
341: if (this.hasNew || this.hasChanges || this.hasDeleted) {
342: calculateChanges();
343: }
344: validateIntegrityConstraints();
345: storageCommit();
346: }
347:
348: private void validateIntegrityConstraints() {
349: final IntegrityConstraintsValidator validator = getValidator();
350: for (ObjectChangeSet changeSet : uowChangeSet.getNewObjects()) {
351: validator.validate(changeSet.getCloneObject(), entityType((Class<Object>) changeSet.getObjectClass()), isNotInferred());
352: }
353: uowChangeSet.getExistingObjectsChanges().forEach(changeSet -> validator.validate(changeSet, getMetamodel()));
354: }
355:
356: private static Map<Object, Object> createMap() {
357: return new IdentityHashMap<>();
358: }
359:
360: /**
361: * Gets current state of the specified entity.
362: * <p>
363: * Note that since no repository is specified we can only determine if the entity is managed or removed. Therefore
364: * if the case is different this method returns State#NOT_MANAGED.
365: *
366: * @param entity The entity to check
367: * @return State of the entity
368: */
369: public State getState(Object entity) {
370: Objects.requireNonNull(entity);
371:
372: if (deletedObjects.containsKey(entity)) {
373: return State.REMOVED;
374: } else if (newObjectsCloneToOriginal.containsKey(entity)) {
375: return State.MANAGED_NEW;
376: } else if (cloneMapping.contains(entity)) {
377: return State.MANAGED;
378: } else {
379: return State.NOT_MANAGED;
380: }
381: }
382:
383: /**
384: * Checks the state of the specified entity with regards to the specified repository.
385: *
386: * @param entity Object
387: * @param descriptor Entity descriptor
388: * @return The state of the specified entity
389: */
390: public State getState(Object entity, Descriptor descriptor) {
391: Objects.requireNonNull(entity);
392: Objects.requireNonNull(descriptor);
393:
394: if (deletedObjects.containsKey(entity)) {
395: return State.REMOVED;
396: } else if (newObjectsCloneToOriginal.containsKey(entity) && isInRepository(descriptor, entity)) {
397: return State.MANAGED_NEW;
398: } else if (cloneMapping.contains(entity) && isInRepository(descriptor, entity)) {
399: return State.MANAGED;
400: } else {
401: return State.NOT_MANAGED;
402: }
403: }
404:
405: /**
406: * Tries to find the original object for the given clone. It searches the existing objects, new objects and deleted
407: * objects.
408: *
409: * @param clone Object
410: * @return The original object for the given clone
411: */
412: public Object getOriginal(Object clone) {
413: if (clone == null) {
414: return null;
415: }
416: return cloneToOriginals.containsKey(clone) ? cloneToOriginals.get(clone) : newObjectsCloneToOriginal.get(clone);
417: }
418:
419: /**
420: * Gets managed original with the specified identifier or {@code null} if there is none matching.
421: * <p>
422: * Descriptor is used to check repository context validity.
423: *
424: * @param cls Return type of the original
425: * @param identifier Instance identifier
426: * @param descriptor Repository descriptor
427: * @return Original object managed by this UoW or {@code null} if this UoW doesn't contain a matching instance
428: */
429: public <T> T getManagedOriginal(Class<T> cls, Object identifier, Descriptor descriptor) {
430: final T clone = getManagedClone(cls, identifier, descriptor);
431: return clone != null ? cls.cast(cloneToOriginals.get(clone)) : null;
432: }
433:
434: /**
435: * Check if this UnitOfWork contains this original entity. This method is used by the CloneBuilder so it does not
436: * have to clone already managed referenced objects.
437: *
438: * @param entity The original entity.
439: * @return True if the original is managed in this UnitOfWork.
440: */
441: boolean containsOriginal(Object entity) {
442: return entity != null && cloneToOriginals.containsValue(entity);
443: }
444:
445: /**
446: * Finds clone of the specified original.
447: *
448: * @param original The original object whose clone we are looking for
449: * @return The clone or null, if there is none
450: */
451: public Object getCloneForOriginal(Object original) {
452: for (Entry<Object, Object> entry : cloneToOriginals.entrySet()) {
453: // We use IdentityMap, so we can use ==
454: if (entry.getValue() == original) {
455: return entry.getKey();
456: }
457: }
458: return null;
459: }
460:
461: public boolean hasChanges() {
462: return hasChanges || hasDeleted || hasNew;
463: }
464:
465: void setHasChanges() {
466: this.hasChanges = true;
467: }
468:
469: @Override
470: public CacheManager getLiveObjectCache() {
471: return parent.getLiveObjectCache();
472: }
473:
474: UnitOfWorkChangeSet getUowChangeSet() {
475: return uowChangeSet;
476: }
477:
478: @Override
479: public boolean isActive() {
480: return this.isActive;
481: }
482:
483: /**
484: * Returns true if the given clone represents a newly created object. Otherwise returns false.
485: *
486: * @param clone Object
487: * @return boolean
488: */
489: public boolean isObjectNew(Object clone) {
490: return clone != null && newObjectsCloneToOriginal.containsKey(clone);
491: }
492:
493: /**
494: * Returns true if the given object is already managed.
495: *
496: * @param entity Object
497: * @return boolean
498: */
499: @Override
500: public boolean isObjectManaged(Object entity) {
501: Objects.requireNonNull(entity);
502:
503: return cloneMapping.contains(entity) && !deletedObjects.containsKey(entity) ||
504: newObjectsCloneToOriginal.containsKey(entity);
505: }
506:
507: /**
508: * Persists changed value of the specified field.
509: *
510: * @param entity Entity with changes (the clone)
511: * @param f The field whose value has changed
512: * @throws IllegalStateException If this UoW is not in transaction
513: */
514: public void attributeChanged(Object entity, Field f) {
515: if (!isInTransaction()) {
516: throw new IllegalStateException("This unit of work is not in a transaction.");
517: }
518: final Descriptor descriptor = getDescriptor(entity);
519: final EntityTypeImpl<Object> et = entityType((Class<Object>) entity.getClass());
520: et.getLifecycleListenerManager().invokePreUpdateCallbacks(entity);
521: storage.merge(entity, f, descriptor);
522: createAndRegisterChangeRecord(entity, et.getFieldSpecification(f.getName()), descriptor);
523: setHasChanges();
524: setIndirectObjectIfPresent(entity, f);
525: et.getLifecycleListenerManager().invokePostUpdateCallbacks(entity);
526: instanceDescriptors.get(entity).setLoaded(et.getFieldSpecification(f.getName()), LoadState.LOADED);
527: }
528:
529: private void createAndRegisterChangeRecord(Object clone, FieldSpecification<?, ?> fieldSpec,
530: Descriptor descriptor) {
531: final Object orig = getOriginal(clone);
532: if (orig == null) {
533: return;
534: }
535: final ChangeRecord record = new ChangeRecordImpl(fieldSpec,
536: EntityPropertiesUtils.getFieldValue(fieldSpec.getJavaField(), clone));
537: preventCachingIfReferenceIsNotLoaded(record);
538: registerChangeRecord(clone, orig, descriptor, record);
539: }
540:
541: private void preventCachingIfReferenceIsNotLoaded(ChangeRecord changeRecord) {
542: final Object newValue = changeRecord.getNewValue();
543: if (newValue != null && contains(newValue) && isLoaded(newValue) != LoadState.LOADED) {
544: changeRecord.preventCaching();
545: }
546: }
547:
548: private void registerChangeRecord(Object clone, Object orig, Descriptor descriptor, ChangeRecord record) {
549: ObjectChangeSet chSet = uowChangeSet.getExistingObjectChanges(orig);
550: if (chSet == null) {
551: chSet = ChangeSetFactory.createObjectChangeSet(orig, clone, descriptor);
552: uowChangeSet.addObjectChangeSet(chSet);
553: }
554: chSet.addChangeRecord(record);
555: }
556:
557: /**
558: * Merge the changes from this Unit of Work's change set into the server session.
559: */
560: private void mergeChangesIntoParent() {
561: if (hasChanges()) {
562: mergeManager.mergeChangesFromChangeSet(uowChangeSet);
563: }
564: evictPossiblyUpdatedReferencesFromCache();
565: }
566:
567: private void evictPossiblyUpdatedReferencesFromCache() {
568: cloneToOriginals.forEach((clone, orig) -> {
569: if (orig == null && !deletedObjects.containsKey(clone)) {
570: removeObjectFromCache(clone, getDescriptor(clone).getSingleContext().orElse(null));
571: }
572: });
573: }
574:
575: @Override
576: public <T> T mergeDetached(T entity, Descriptor descriptor) {
577: Objects.requireNonNull(entity);
578: Objects.requireNonNull(descriptor);
579:
580: final Object id = getIdentifier(entity);
581: if (!storage.contains(id, entity.getClass(), descriptor)) {
582: registerNewObject(entity, descriptor);
583: return entity;
584: } else {
585: if (isIndividualManaged(id, entity) && !isSameType(id, entity)) {
586: throw individualAlreadyManaged(id);
587: }
588: return mergeDetachedInternal(entity, descriptor);
589: }
590: }
591:
592: private boolean isSameType(Object id, Object entity) {
593: final Class<?> mergedType = entity.getClass();
594: final Object managed = keysToClones.containsKey(id) ? keysToClones.get(id) : newObjectsKeyToClone.get(id);
595: return managed != null && managed.getClass().isAssignableFrom(mergedType);
596: }
597:
598: private <T> T mergeDetachedInternal(T entity, Descriptor descriptor) {
599: assert entity != null;
600: final EntityTypeImpl<T> et = (EntityTypeImpl<T>) entityType(entity.getClass());
601: final URI idUri = EntityPropertiesUtils.getIdentifier(entity, et);
602:
603: final Object clone = getInstanceForMerge(idUri, et, descriptor);
604: try {
605: // Merge only the changed attributes
606: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(clone, entity, descriptor);
607: changeManager.calculateChanges(chSet);
608: if (chSet.hasChanges()) {
609: et.getLifecycleListenerManager().invokePreUpdateCallbacks(clone);
610: final DetachedInstanceMerger merger = new DetachedInstanceMerger(this);
611: merger.mergeChangesFromDetachedToManagedInstance(chSet, descriptor);
612: for (ChangeRecord record : chSet.getChanges()) {
613: AttributeModificationValidator.verifyCanModify(record.getAttribute());
614: preventCachingIfReferenceIsNotLoaded(record);
615: final Field field = record.getAttribute().getJavaField();
616: storage.merge(clone, field, descriptor);
617: }
618: et.getLifecycleListenerManager().invokePostUpdateCallbacks(clone);
619: uowChangeSet.addObjectChangeSet(copyChangeSet(chSet, getOriginal(clone), clone, descriptor));
620: }
621: } catch (OWLEntityExistsException e) {
622: unregisterObject(clone);
623: throw e;
624: }
625: evictAfterMerge(et, idUri, descriptor);
626: setHasChanges();
627: checkForIndirectObjects(clone);
628: return et.getJavaType().cast(clone);
629: }
630:
631: private <T> Object getInstanceForMerge(URI identifier, EntityType<T> et, Descriptor descriptor) {
632: if (keysToClones.containsKey(identifier)) {
633: return keysToClones.get(identifier);
634: }
635: final LoadingParameters<T> params = new LoadingParameters<>(et.getJavaType(), identifier, descriptor, true);
636: T original = storage.find(params);
637: assert original != null;
638:
639: return registerExistingObject(original, descriptor);
640: }
641:
642: private static ObjectChangeSet copyChangeSet(ObjectChangeSet changeSet, Object original, Object clone,
643: Descriptor descriptor) {
644: final ObjectChangeSet newChangeSet = ChangeSetFactory.createObjectChangeSet(original, clone, descriptor);
645: changeSet.getChanges().forEach(newChangeSet::addChangeRecord);
646: return newChangeSet;
647: }
648:
649: private void evictAfterMerge(EntityType<?> et, URI identifier, Descriptor descriptor) {
650: if (cacheManager.contains(et.getJavaType(), identifier, descriptor)) {
651: cacheManager.evict(et.getJavaType(), identifier, descriptor.getSingleContext().orElse(null));
652: }
653: getMetamodel().getReferringTypes(et.getJavaType()).forEach(cacheManager::evict);
654: }
655:
656: private void registerEntityWithPersistenceContext(Object entity) {
657: Aspects.aspectOf(BeanListenerAspect.class).register(entity, this);
658: }
659:
660: private void deregisterEntityFromPersistenceContext(Object entity) {
661: Aspects.aspectOf(BeanListenerAspect.class).deregister(entity);
662: }
663:
664: @Override
665: public NamedQueryManager getNamedQueryManager() {
666: return parent.getNamedQueryManager();
667: }
668:
669: @Override
670: public ResultSetMappingManager getResultSetMappingManager() {
671: return parent.getResultSetMappingManager();
672: }
673:
674: @Override
675: public Object registerExistingObject(Object entity, Descriptor descriptor) {
676: return registerExistingObject(entity, descriptor, Collections.emptyList());
677: }
678:
679: @Override
680: public Object registerExistingObject(Object entity, Descriptor descriptor, List<Consumer<Object>> postClone) {
681: if (entity == null) {
682: return null;
683: }
684: if (cloneToOriginals.containsValue(entity)) {
685: return getCloneForOriginal(entity);
686: }
687: final CloneConfiguration cloneConfig = new CloneConfiguration(descriptor);
688: postClone.forEach(cloneConfig::addPostRegisterHandler);
689: Object clone = cloneBuilder.buildClone(entity, cloneConfig);
690: assert clone != null;
691: registerClone(clone, entity, descriptor);
692: postClone.forEach(c -> c.accept(clone));
693: return clone;
694: }
695:
696: private void registerClone(Object clone, Object original, Descriptor descriptor) {
697: cloneToOriginals.put(clone, original);
698: final Object identifier = EntityPropertiesUtils.getIdentifier(clone, getMetamodel());
699: keysToClones.put(identifier, clone);
700: instanceDescriptors
701: .put(clone, InstanceDescriptorFactory.create(clone, (EntityType<Object>) entityType(clone.getClass())));
702: registerEntityWithPersistenceContext(clone);
703: registerEntityWithOntologyContext(clone, descriptor);
704: }
705:
706: /**
707: * Release this Unit of Work. Releasing an active Unit of Work with uncommitted changes causes all pending changes
708: * to be discarded.
709: */
710: @Override
711: public void release() {
712: clear();
713: storage.close();
714: this.isActive = false;
715: LOG.debug("UnitOfWork released.");
716: }
717:
718: @Override
719: public <T> void refreshObject(T object) {
720: Objects.requireNonNull(object);
721: if (!isObjectManaged(object)) {
722: throw new IllegalArgumentException(
723: "Cannot call refresh on an instance not managed by this persistence context.");
724: }
725: final EntityTypeImpl<T> et = entityType((Class<T>) object.getClass());
726: final URI idUri = EntityPropertiesUtils.getIdentifier(object, et);
727: final Descriptor descriptor = getDescriptor(object);
728:
729: final LoadingParameters<T> params = new LoadingParameters<>(et.getJavaType(), idUri, descriptor, true);
730: params.bypassCache();
731: final ConnectionWrapper connection = acquireConnection();
732: try {
733: uowChangeSet.cancelObjectChanges(getOriginal(object));
734: T original = connection.find(params);
735: if (original == null) {
736: throw new EntityNotFoundException("Entity " + object + " no longer exists in the repository.");
737: }
738: T source = (T) cloneBuilder.buildClone(original, new CloneConfiguration(descriptor));
739: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(source, object, descriptor);
740: changeManager.calculateChanges(chSet);
741: new RefreshInstanceMerger(indirectWrapperHelper).mergeChanges(chSet);
742: revertTransactionalChanges(object, descriptor, chSet);
743: registerClone(object, original, descriptor);
744: et.getLifecycleListenerManager().invokePostLoadCallbacks(object);
745: } finally {
746: connection.close();
747: }
748: }
749:
750: private <T> void revertTransactionalChanges(T object, Descriptor descriptor, ObjectChangeSet chSet) {
751: for (ChangeRecord change : chSet.getChanges()) {
752: storage.merge(object, change.getAttribute().getJavaField(),
753: descriptor.getAttributeDescriptor(change.getAttribute()));
754: }
755: }
756:
757: @Override
758: public void registerNewObject(Object entity, Descriptor descriptor) {
759: Objects.requireNonNull(entity);
760: Objects.requireNonNull(descriptor);
761:
762: registerNewObjectInternal(entity, descriptor);
763: }
764:
765: /**
766: * Registers the specified entity for persist in this Unit of Work.
767: *
768: * @param entity The entity to register
769: * @param descriptor Entity descriptor, specifying optionally contexts into which the entity will be persisted
770: */
771: private void registerNewObjectInternal(Object entity, Descriptor descriptor) {
772: final EntityTypeImpl<?> eType = entityType(entity.getClass());
773: eType.getLifecycleListenerManager().invokePrePersistCallbacks(entity);
774: Object id = getIdentifier(entity);
775:• if (id == null) {
776: EntityPropertiesUtils.verifyIdentifierIsGenerated(entity, eType);
777: }
778: verifyCanPersist(id, entity, eType, descriptor);
779: storage.persist(id, entity, descriptor);
780:• if (id == null) {
781: // If the ID was null, extract it from the entity. It is present now
782: id = getIdentifier(entity);
783: }
784:• assert id != null;
785: // Original is null until commit
786: newObjectsCloneToOriginal.put(entity, null);
787: registerEntityWithPersistenceContext(entity);
788: registerEntityWithOntologyContext(entity, descriptor);
789: instanceDescriptors.put(entity, InstanceDescriptorFactory.createAllLoaded(entity, (EntityType<Object>) eType));
790: newObjectsKeyToClone.put(id, entity);
791: checkForIndirectObjects(entity);
792: this.hasNew = true;
793: eType.getLifecycleListenerManager().invokePostPersistCallbacks(entity);
794: }
795:
796: private void verifyCanPersist(Object id, Object instance, EntityType<?> et, Descriptor descriptor) {
797: if (isIndividualManaged(id, instance) && !instance.getClass().isEnum()) {
798: throw individualAlreadyManaged(id);
799: }
800: if (storage.contains(id, instance.getClass(), descriptor)) {
801: throw new OWLEntityExistsException(
802: "Individual " + id + " of type " + et.getIRI() + " already exists in storage.");
803: }
804: }
805:
806: private boolean isIndividualManaged(Object identifier, Object entity) {
807: return keysToClones.containsKey(identifier) ||
808: newObjectsKeyToClone.containsKey(identifier) && !cloneMapping.contains(entity);
809: }
810:
811: @Override
812: public void removeObject(Object entity) {
813: assert entity != null;
814: if (!isObjectManaged(entity)) {
815: throw new IllegalArgumentException(
816: "Cannot remove entity which is not managed in the current persistence context.");
817: }
818: final EntityTypeImpl<?> et = entityType(entity.getClass());
819: et.getLifecycleListenerManager().invokePreRemoveCallbacks(entity);
820: final Object primaryKey = getIdentifier(entity);
821: final Descriptor descriptor = getDescriptor(entity);
822:
823: if (hasNew && newObjectsCloneToOriginal.containsKey(entity)) {
824: unregisterObject(entity);
825: newObjectsKeyToClone.remove(primaryKey);
826: } else {
827: deletedObjects.put(entity, entity);
828: this.hasDeleted = true;
829: }
830: storage.remove(primaryKey, et.getJavaType(), descriptor);
831: et.getLifecycleListenerManager().invokePostRemoveCallbacks(entity);
832: }
833:
834: @Override
835: public void restoreRemovedObject(Object entity) {
836: assert deletedObjects.containsKey(entity);
837:
838: deletedObjects.remove(entity);
839: final Object id = getIdentifier(entity);
840: storage.persist(id, entity, getDescriptor(entity));
841: }
842:
843: /**
844: * Remove the registered object from this Unit of Work.
845: *
846: * @param object Clone of the original object
847: */
848: public void unregisterObject(Object object) {
849: if (object == null) {
850: return;
851: }
852: final Object original = cloneToOriginals.remove(object);
853: keysToClones.remove(EntityPropertiesUtils.getIdentifier(object, getMetamodel()));
854:
855: deletedObjects.remove(object);
856: if (hasNew) {
857: newObjectsCloneToOriginal.remove(object);
858: }
859: if (original != null) {
860: cloneBuilder.removeVisited(original, repoMap.getEntityDescriptor(object));
861: }
862: removeIndirectCollections(object);
863: deregisterEntityFromPersistenceContext(object);
864: unregisterEntityFromOntologyContext(object);
865: }
866:
867: @Override
868: public boolean shouldReleaseAfterCommit() {
869: return shouldReleaseAfterCommit;
870: }
871:
872: public void setShouldClearAfterCommit(boolean shouldClearCache) {
873: this.shouldClearCacheAfterCommit = shouldClearCache;
874: }
875:
876: public void setEntityManager(AbstractEntityManager entityManager) {
877: this.entityManager = entityManager;
878: }
879:
880: @Override
881: public void writeUncommittedChanges() {
882: if (hasChanges()) {
883: commitUnitOfWork();
884: }
885: }
886:
887: @Override
888: public MetamodelImpl getMetamodel() {
889: return parent.getMetamodel();
890: }
891:
892: private <T> EntityTypeImpl<T> entityType(Class<T> cls) {
893: return getMetamodel().entity(cls);
894: }
895:
896: @Override
897: public boolean isEntityType(Class<?> cls) {
898: return parent.isEntityType(cls);
899: }
900:
901: @Override
902: public boolean isInTransaction() {
903: return entityManager != null && entityManager.getTransaction().isActive();
904: }
905:
906: /**
907: * Returns {@code true} if this UoW is currently committing changes.
908: *
909: * @return Whether this UoW is in the commit phase
910: */
911: public boolean isInCommit() {
912: return inCommit;
913: }
914:
915: @Override
916: public <T> void loadEntityField(T entity, Field field) {
917: Objects.requireNonNull(entity);
918: Objects.requireNonNull(field);
919: assert field.getDeclaringClass().isAssignableFrom(entity.getClass());
920:
921: final Descriptor entityDescriptor = getDescriptor(entity);
922: if (!instanceDescriptors.containsKey(entity)) {
923: throw new OWLPersistenceException(
924: "Unable to find repository identifier for entity " + entity + ". Is it managed by this UoW?");
925: }
926: final InstanceDescriptor<?> instanceDescriptor = instanceDescriptors.get(entity);
927: final FieldSpecification<?, ?> fieldSpec = entityType((Class<Object>) entity.getClass())
928: .getFieldSpecification(field.getName());
929: if (instanceDescriptor.isLoaded(fieldSpec) == LoadState.LOADED) {
930: return;
931: }
932:
933: storage.loadFieldValue(entity, field, entityDescriptor);
934: final Object orig = EntityPropertiesUtils.getFieldValue(field, entity);
935: final Object entityOriginal = getOriginal(entity);
936: if (entityOriginal != null) {
937: EntityPropertiesUtils.setFieldValue(field, entityOriginal, orig);
938: }
939: final Descriptor fieldDescriptor = getFieldDescriptor(entity, field, entityDescriptor);
940: final Object clone = cloneLoadedFieldValue(entity, field, fieldDescriptor, orig);
941: EntityPropertiesUtils.setFieldValue(field, entity, clone);
942: instanceDescriptors.get(entity).setLoaded(fieldSpec, LoadState.LOADED);
943: }
944:
945: private <T> Descriptor getFieldDescriptor(T entity, Field field, Descriptor entityDescriptor) {
946: final EntityType<?> et = entityType(entity.getClass());
947: final FieldSpecification<?, ?> fieldSpec = et.getFieldSpecification(field.getName());
948: return entityDescriptor.getAttributeDescriptor(fieldSpec);
949: }
950:
951: private <T> Object cloneLoadedFieldValue(T entity, Field field, final Descriptor fieldDescriptor,
952: final Object fieldValueOrig) {
953: Object clone;
954: if (fieldValueOrig == null) {
955: clone = null;
956: } else {
957: if (isEntityType(field.getType())) {
958: clone = registerExistingObject(fieldValueOrig, fieldDescriptor);
959: putObjectIntoCache(getIdentifier(clone), fieldValueOrig, fieldDescriptor);
960: } else {
961: clone = cloneBuilder.buildClone(entity, field, fieldValueOrig, fieldDescriptor);
962: }
963: }
964: return clone;
965: }
966:
967: @Override
968: public void removeObjectFromCache(Object toRemove, URI context) {
969: Objects.requireNonNull(toRemove);
970:
971: cacheManager.evict(toRemove.getClass(), getIdentifier(toRemove), context);
972: }
973:
974: @Override
975: public boolean isConsistent(URI context) {
976: return storage.isConsistent(context);
977: }
978:
979: @Override
980: public List<URI> getContexts() {
981: return storage.getContexts();
982: }
983:
984: @Override
985: public LoadState isLoaded(Object entity, String attributeName) {
986: Objects.requireNonNull(entity);
987: final FieldSpecification<?, ?> fs = entityType(entity.getClass()).getFieldSpecification(attributeName);
988: return instanceDescriptors.containsKey(entity) ? instanceDescriptors.get(entity).isLoaded(fs) :
989: LoadState.UNKNOWN;
990: }
991:
992: @Override
993: public LoadState isLoaded(Object entity) {
994: Objects.requireNonNull(entity);
995: return instanceDescriptors.containsKey(entity) ? instanceDescriptors.get(entity).isLoaded() : LoadState.UNKNOWN;
996: }
997:
998: @Override
999: public void setUseTransactionalOntologyForQueryProcessing() {
1000: this.useTransactionalOntology = true;
1001: }
1002:
1003: @Override
1004: public boolean useTransactionalOntologyForQueryProcessing() {
1005: return useTransactionalOntology;
1006: }
1007:
1008: @Override
1009: public void setUseBackupOntologyForQueryProcessing() {
1010: this.useTransactionalOntology = false;
1011: }
1012:
1013: @Override
1014: public boolean useBackupOntologyForQueryProcessing() {
1015: return !useTransactionalOntology;
1016: }
1017:
1018: public SparqlQueryFactory sparqlQueryFactory() {
1019: return queryFactory;
1020: }
1021:
1022: public CriteriaBuilder criteriaFactory() {
1023: return criteriaFactory;
1024: }
1025:
1026: /**
1027: * Check if the specified entity contains a collection. If so, replace it with its indirect representation so that
1028: * changes in that collection can be tracked.
1029: *
1030: * @param entity The entity to check
1031: */
1032: private void checkForIndirectObjects(Object entity) {
1033: assert entity != null;
1034: final EntityType<?> et = entityType(entity.getClass());
1035: for (FieldSpecification<?, ?> fieldSpec : et.getFieldSpecifications()) {
1036: setIndirectObjectIfPresent(entity, fieldSpec.getJavaField());
1037: }
1038: }
1039:
1040: /**
1041: * Create and set indirect collection on the specified entity field.
1042: * <p>
1043: * If the specified field is of Collection type and it is not already an indirect collection, create new one and set
1044: * it as the value of the specified field on the specified entity.
1045: *
1046: * @param entity The entity collection will be set on
1047: * @param field The field to set
1048: * @throws IllegalArgumentException Reflection
1049: */
1050: private void setIndirectObjectIfPresent(Object entity, Field field) {
1051: assert entity != null;
1052: assert field != null;
1053:
1054: final Object value = EntityPropertiesUtils.getFieldValue(field, entity);
1055: if (value instanceof IndirectWrapper) {
1056: return;
1057: }
1058: if (IndirectWrapperHelper.requiresIndirectWrapper(value)) {
1059: EntityPropertiesUtils
1060: .setFieldValue(field, entity, indirectWrapperHelper.createIndirectWrapper(value, entity, field));
1061: }
1062: }
1063:
1064: /**
1065: * Creates an indirect collection, which wraps the specified collection instance and propagates changes to the
1066: * persistence context.
1067: *
1068: * @param collection Collection to be proxied
1069: * @param owner Collection owner instance
1070: * @param field Field filled with the collection
1071: * @return Indirect collection
1072: */
1073: public Object createIndirectCollection(Object collection, Object owner, Field field) {
1074: return indirectWrapperHelper.createIndirectWrapper(collection, owner, field);
1075: }
1076:
1077: /**
1078: * Remove indirect collection implementations from the specified entity (if present).
1079: *
1080: * @param entity The entity to remove indirect collections from
1081: */
1082: private void removeIndirectCollections(Object entity) {
1083: assert entity != null;
1084: final EntityType<?> et = entityType(entity.getClass());
1085: for (FieldSpecification<?, ?> fs : et.getFieldSpecifications()) {
1086: final Object value = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), entity);
1087: if (value instanceof IndirectCollection) {
1088: IndirectCollection<?> indCol = (IndirectCollection<?>) value;
1089: EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, indCol.unwrap());
1090: }
1091: }
1092: }
1093:
1094: void putObjectIntoCache(Object identifier, Object entity, Descriptor descriptor) {
1095: cacheManager.add(identifier, entity, descriptor);
1096: }
1097:
1098: private Object getIdentifier(Object entity) {
1099: return EntityPropertiesUtils.getIdentifier(entity, getMetamodel());
1100: }
1101:
1102: private void unregisterEntityFromOntologyContext(Object entity) {
1103: assert entity != null;
1104:
1105: final Descriptor descriptor = repoMap.getEntityDescriptor(entity);
1106: if (descriptor == null) {
1107: throw new OWLPersistenceException("Fatal error, unable to find descriptor for entity " + entity);
1108: }
1109:
1110: repoMap.remove(descriptor, entity);
1111: repoMap.removeEntityToRepository(entity);
1112: }
1113:
1114: private void registerEntityWithOntologyContext(Object entity, Descriptor descriptor) {
1115: assert descriptor != null;
1116: assert entity != null;
1117:
1118: repoMap.add(descriptor, entity, null);
1119: repoMap.addEntityToRepository(entity, descriptor);
1120: }
1121:
1122: private boolean isInRepository(Descriptor descriptor, Object entity) {
1123: assert descriptor != null;
1124: assert entity != null;
1125:
1126: return repoMap.contains(descriptor, entity);
1127: }
1128:
1129: private Descriptor getDescriptor(Object entity) {
1130: assert entity != null;
1131:
1132: final Descriptor descriptor = repoMap.getEntityDescriptor(entity);
1133: if (descriptor == null) {
1134: throw new OWLPersistenceException("Unable to find descriptor of entity " + entity + " in this UoW!");
1135: }
1136: return descriptor;
1137: }
1138:
1139: private void storageCommit() {
1140: try {
1141: storage.commit();
1142: } catch (OWLPersistenceException e) {
1143: entityManager.removeCurrentPersistenceContext();
1144: throw e;
1145: }
1146: }
1147:
1148: @Override
1149: public <T> T unwrap(Class<T> cls) {
1150: if (cls.isAssignableFrom(getClass())) {
1151: return cls.cast(this);
1152: }
1153: return storage.unwrap(cls);
1154: }
1155:
1156: public SparqlQueryFactory getQueryFactory() {
1157: return queryFactory;
1158: }
1159: }