Skip to content

Method: attributeChanged(Object, Field)

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