Skip to contentMethod: readObject(Class, Object, Descriptor)
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 Set<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.cloneToOriginals = createMap();
88: this.cloneMapping = cloneToOriginals.keySet();
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: cloneToOriginals.clear();
217: keysToClones.clear();
218: this.deletedObjects = null;
219: this.newObjectsCloneToOriginal = null;
220: this.newObjectsOriginalToClone = null;
221: this.newObjectsKeyToClone.clear();
222: this.hasChanges = false;
223: this.hasDeleted = false;
224: this.hasNew = false;
225: cloneBuilder.reset();
226: this.repoMap = new RepositoryMap();
227: repoMap.initDescriptors();
228: this.uowChangeSet = ChangeSetFactory.createUoWChangeSet();
229: }
230:
231: private void detachAllManagedInstances() {
232: cloneMapping.forEach(instance -> {
233: removeIndirectCollections(instance);
234: deregisterEntityFromPersistenceContext(instance);
235: });
236: getNewObjectsCloneToOriginal().keySet().forEach(instance -> {
237: removeIndirectCollections(instance);
238: deregisterEntityFromPersistenceContext(instance);
239: });
240: }
241:
242: @Override
243: public boolean contains(Object entity) {
244: Objects.requireNonNull(entity);
245:
246: return isObjectManaged(entity);
247: }
248:
249: @Override
250: public void commit() {
251: LOG.trace("UnitOfWork commit started.");
252: if (!isActive()) {
253: throw new IllegalStateException("Cannot commit inactive Unit of Work!");
254: }
255: this.inCommit = true;
256: commitUnitOfWork();
257: LOG.trace("UnitOfWork commit finished.");
258: }
259:
260: @Override
261: public void rollback() {
262: LOG.trace("UnitOfWork rollback started.");
263: if (!isActive()) {
264: throw new IllegalStateException("Cannot rollback inactive Unit of Work!");
265: }
266: storage.rollback();
267: clear();
268: }
269:
270: /**
271: * Commit this Unit of Work.
272: */
273: private void commitUnitOfWork() {
274: commitToOntology();
275: mergeChangesIntoParent();
276: postCommit();
277: }
278:
279: /**
280: * Clean up after the commit.
281: */
282: private void postCommit() {
283: clear();
284: this.inCommit = false;
285: if (shouldClearCacheAfterCommit) {
286: cacheManager.evictAll();
287: this.shouldReleaseAfterCommit = true;
288: }
289: }
290:
291: /**
292: * If there are any changes, commit them to the ontology.
293: */
294: private void commitToOntology() {
295: if (this.hasNew || this.hasChanges || this.hasDeleted) {
296: calculateChanges();
297: }
298: validateIntegrityConstraints();
299: storageCommit();
300: }
301:
302: private void validateIntegrityConstraints() {
303: final IntegrityConstraintsValidator validator = IntegrityConstraintsValidator.getValidator();
304: for (ObjectChangeSet changeSet : uowChangeSet.getNewObjects()) {
305: validator.validate(changeSet.getCloneObject(),
306: entityType((Class<Object>) changeSet.getObjectClass()), false);
307: }
308: uowChangeSet.getExistingObjectsChanges().forEach(changeSet -> validator.validate(changeSet, getMetamodel()));
309: }
310:
311: private static Map<Object, Object> createMap() {
312: return new IdentityHashMap<>();
313: }
314:
315: /**
316: * Gets current state of the specified entity.
317: * <p>
318: * Note that since no repository is specified we can only determine if the entity is managed or removed. Therefore
319: * if the case is different this method returns State#NOT_MANAGED.
320: *
321: * @param entity The entity to check
322: * @return State of the entity
323: */
324: public State getState(Object entity) {
325: Objects.requireNonNull(entity);
326:
327: if (getDeletedObjects().containsKey(entity)) {
328: return State.REMOVED;
329: } else if (getNewObjectsCloneToOriginal().containsKey(entity)) {
330: return State.MANAGED_NEW;
331: } else if (cloneMapping.contains(entity)) {
332: return State.MANAGED;
333: } else {
334: return State.NOT_MANAGED;
335: }
336: }
337:
338: /**
339: * Checks the state of the specified entity with regards to the specified repository.
340: *
341: * @param entity Object
342: * @param descriptor Entity descriptor
343: * @return The state of the specified entity
344: */
345: public State getState(Object entity, Descriptor descriptor) {
346: Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity"));
347: Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor"));
348:
349: if (getDeletedObjects().containsKey(entity)) {
350: return State.REMOVED;
351: } else if (getNewObjectsCloneToOriginal().containsKey(entity) && isInRepository(descriptor, entity)) {
352: return State.MANAGED_NEW;
353: }
354: else if (cloneMapping.contains(entity) && isInRepository(descriptor, entity)) {
355: return State.MANAGED;
356: } else {
357: return State.NOT_MANAGED;
358: }
359: }
360:
361: /**
362: * Tries to find the original object for the given clone. It searches the existing objects, new objects and deleted
363: * objects.
364: *
365: * @param clone Object
366: * @return The original object for the given clone
367: */
368: public Object getOriginal(Object clone) {
369: if (clone == null) {
370: return null;
371: }
372: Object original = cloneToOriginals.get(clone);
373: if (original == null) {
374: original = getNewObjectsCloneToOriginal().get(clone);
375: }
376: return original;
377: }
378:
379: /**
380: * Gets managed original with the specified identifier or {@code null} if there is none matching.
381: * <p>
382: * Descriptor is used to check repository context validity.
383: *
384: * @param cls Return type of the original
385: * @param identifier Instance identifier
386: * @param descriptor Repository descriptor
387: * @return Original object managed by this UoW or {@code null} if this UoW doesn't contain a matching instance
388: */
389: public <T> T getManagedOriginal(Class<T> cls, Object identifier, Descriptor descriptor) {
390: if (!keysToClones.containsKey(identifier)) {
391: return null;
392: }
393: final Object clone = keysToClones.get(identifier);
394: if (!cls.isAssignableFrom(clone.getClass())) {
395: return null;
396: }
397: if (!isInRepository(descriptor, clone)) {
398: return null;
399: }
400: return cls.cast(cloneToOriginals.get(clone));
401: }
402:
403: /**
404: * Check if this UnitOfWork contains this original entity. This method is used by the CloneBuilder so it does not
405: * have to clone already managed referenced objects.
406: *
407: * @param entity The original entity.
408: * @return True if the original is managed in this UnitOfWork.
409: */
410: boolean containsOriginal(Object entity) {
411: return entity != null && cloneToOriginals.containsValue(entity);
412: }
413:
414: /**
415: * Finds clone of the specified original.
416: *
417: * @param original The original object whose clone we are looking for
418: * @return The clone or null, if there is none
419: */
420: public Object getCloneForOriginal(Object original) {
421: for (Entry<Object, Object> entry : cloneToOriginals.entrySet()) {
422: // We use IdentityMap, so we can use ==
423: if (entry.getValue() == original) {
424: return entry.getKey();
425: }
426: }
427: return null;
428: }
429:
430: public boolean hasChanges() {
431: return hasChanges || hasDeleted || hasNew;
432: }
433:
434: void setHasChanges() {
435: this.hasChanges = true;
436: }
437:
438: Map<Object, Object> getDeletedObjects() {
439: if (deletedObjects == null) {
440: this.deletedObjects = createMap();
441: }
442: return deletedObjects;
443: }
444:
445: Map<Object, Object> getNewObjectsCloneToOriginal() {
446: if (newObjectsCloneToOriginal == null) {
447: this.newObjectsCloneToOriginal = createMap();
448: }
449: return newObjectsCloneToOriginal;
450: }
451:
452: private Map<Object, Object> getNewObjectsOriginalToClone() {
453: if (newObjectsOriginalToClone == null) {
454: this.newObjectsOriginalToClone = createMap();
455: }
456: return newObjectsOriginalToClone;
457: }
458:
459: @Override
460: public CacheManager getLiveObjectCache() {
461: return parent.getLiveObjectCache();
462: }
463:
464: UnitOfWorkChangeSet getUowChangeSet() {
465: return uowChangeSet;
466: }
467:
468: @Override
469: public boolean isActive() {
470: return this.isActive;
471: }
472:
473: /**
474: * Returns true if the given clone represents a newly created object. Otherwise returns false.
475: *
476: * @param clone Object
477: * @return boolean
478: */
479: public boolean isObjectNew(Object clone) {
480: return clone != null && getNewObjectsCloneToOriginal().containsKey(clone);
481: }
482:
483: /**
484: * Returns true if the given object is already managed.
485: *
486: * @param entity Object
487: * @return boolean
488: */
489: @Override
490: public boolean isObjectManaged(Object entity) {
491: Objects.requireNonNull(entity);
492:
493: return cloneMapping.contains(entity) && !getDeletedObjects().containsKey(entity) || getNewObjectsCloneToOriginal().containsKey(entity);
494: }
495:
496: /**
497: * Persists changed value of the specified field.
498: *
499: * @param entity Entity with changes (the clone)
500: * @param f The field whose value has changed
501: * @throws IllegalStateException If this UoW is not in transaction
502: */
503: public void attributeChanged(Object entity, Field f) {
504: if (!isInTransaction()) {
505: throw new IllegalStateException("This unit of work is not in a transaction.");
506: }
507: final Descriptor descriptor = getDescriptor(entity);
508: if (descriptor == null) {
509: throw new OWLPersistenceException("Unable to find repository for entity " + entity
510: + ". Is it registered in this UoW?");
511: }
512: final EntityTypeImpl<?> et = entityType(entity.getClass());
513: et.getLifecycleListenerManager().invokePreUpdateCallbacks(entity);
514: storage.merge(entity, f, descriptor);
515: createChangeRecord(entity, et.getFieldSpecification(f.getName()), descriptor);
516: setHasChanges();
517: setIndirectCollectionIfPresent(entity, f);
518: et.getLifecycleListenerManager().invokePostUpdateCallbacks(entity);
519: }
520:
521: private void createChangeRecord(Object clone, FieldSpecification<?, ?> fieldSpec, Descriptor descriptor) {
522: final Object orig = getOriginal(clone);
523: if (orig == null) {
524: return;
525: }
526: final ChangeRecord record = new ChangeRecordImpl(fieldSpec,
527: EntityPropertiesUtils.getFieldValue(fieldSpec.getJavaField(), clone));
528: registerChangeRecord(clone, orig, descriptor, record);
529: }
530:
531: private void registerChangeRecord(Object clone, Object orig, Descriptor descriptor, ChangeRecord record) {
532: ObjectChangeSet chSet = uowChangeSet.getExistingObjectChanges(orig);
533: if (chSet == null) {
534: chSet = ChangeSetFactory.createObjectChangeSet(orig, clone, descriptor);
535: uowChangeSet.addObjectChangeSet(chSet);
536: }
537: chSet.addChangeRecord(record);
538: }
539:
540: /**
541: * Merge the changes from this Unit of Work's change set into the server session.
542: */
543: private void mergeChangesIntoParent() {
544: if (hasChanges()) {
545: mergeManager.mergeChangesFromChangeSet(uowChangeSet);
546: }
547: }
548:
549: @Override
550: public <T> T mergeDetached(T entity, Descriptor descriptor) {
551: Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity"));
552: Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor"));
553:
554: final Object id = getIdentifier(entity);
555: if (!storage.contains(id, entity.getClass(), descriptor)) {
556: registerNewObject(entity, descriptor);
557: return entity;
558: } else {
559: if (isIndividualManaged(id, entity) && !isSameType(id, entity)) {
560: throw individualAlreadyManaged(id);
561: }
562: return mergeDetachedInternal(entity, descriptor);
563: }
564: }
565:
566: private boolean isSameType(Object id, Object entity) {
567: final Class<?> mergedType = entity.getClass();
568: final Object managed = keysToClones.containsKey(id) ? keysToClones.get(id) : newObjectsKeyToClone.get(id);
569: return managed != null && managed.getClass().isAssignableFrom(mergedType);
570: }
571:
572: private <T> T mergeDetachedInternal(T entity, Descriptor descriptor) {
573: assert entity != null;
574: final EntityTypeImpl<T> et = (EntityTypeImpl<T>) entityType(entity.getClass());
575: final URI idUri = EntityPropertiesUtils.getIdentifier(entity, et);
576:
577: final Object clone = getInstanceForMerge(idUri, et, descriptor);
578: try {
579: // Merge only the changed attributes
580: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(clone, entity, descriptor);
581: LOG.debug("Clone: " + clone + ", merged entity: " + entity);
582: changeManager.calculateChanges(chSet);
583: if (chSet.hasChanges()) {
584: et.getLifecycleListenerManager().invokePreUpdateCallbacks(clone);
585: final DetachedInstanceMerger merger = new DetachedInstanceMerger(this);
586: merger.mergeChangesFromDetachedToManagedInstance(chSet, descriptor);
587: for (ChangeRecord record : chSet.getChanges()) {
588: final Field field = record.getAttribute().getJavaField();
589: storage.merge(clone, field, descriptor);
590: }
591: et.getLifecycleListenerManager().invokePostUpdateCallbacks(clone);
592: uowChangeSet.addObjectChangeSet(copyChangeSet(chSet, getOriginal(clone), clone, descriptor));
593: }
594: } catch (OWLEntityExistsException e) {
595: unregisterObject(clone);
596: throw e;
597: }
598: if (cacheManager.contains(et.getJavaType(), idUri, descriptor)) {
599: cacheManager.evict(et.getJavaType(), idUri, descriptor.getContext());
600: }
601: setHasChanges();
602: checkForCollections(clone);
603: return et.getJavaType().cast(clone);
604: }
605:
606: private <T> Object getInstanceForMerge(URI identifier, EntityType<T> et, Descriptor descriptor) {
607: if (keysToClones.containsKey(identifier)) {
608: return keysToClones.get(identifier);
609: }
610: final LoadingParameters<T> params = new LoadingParameters<>(et.getJavaType(), identifier, descriptor, true);
611: T original = storage.find(params);
612: assert original != null;
613:
614: return registerExistingObject(original, descriptor);
615: }
616:
617: private ObjectChangeSet copyChangeSet(ObjectChangeSet changeSet, Object original, Object clone,
618: Descriptor descriptor) {
619: final ObjectChangeSet newChangeSet = ChangeSetFactory.createObjectChangeSet(original, clone, descriptor);
620: changeSet.getChanges().forEach(newChangeSet::addChangeRecord);
621: return newChangeSet;
622: }
623:
624: private void registerEntityWithPersistenceContext(Object entity) {
625: Aspects.aspectOf(BeanListenerAspect.class).register(entity, this);
626: }
627:
628: private void deregisterEntityFromPersistenceContext(Object entity) {
629: Aspects.aspectOf(BeanListenerAspect.class).deregister(entity);
630: }
631:
632: @Override
633: public NamedQueryManager getNamedQueryManager() {
634: return parent.getNamedQueryManager();
635: }
636:
637: @Override
638: public ResultSetMappingManager getResultSetMappingManager() {
639: return parent.getResultSetMappingManager();
640: }
641:
642: @Override
643: public Object registerExistingObject(Object entity, Descriptor descriptor) {
644: return registerExistingObject(entity, descriptor, Collections.emptyList());
645: }
646:
647: @Override
648: public Object registerExistingObject(Object entity, Descriptor descriptor, List<Consumer<Object>> postClone) {
649: if (entity == null) {
650: return null;
651: }
652: if (cloneToOriginals.containsValue(entity)) {
653: return getCloneForOriginal(entity);
654: }
655: final CloneConfiguration cloneConfig = new CloneConfiguration(descriptor);
656: postClone.forEach(cloneConfig::addPostRegisterHandler);
657: Object clone = cloneBuilder.buildClone(entity, cloneConfig);
658: assert clone != null;
659: registerClone(clone, entity, descriptor);
660: postClone.forEach(c -> c.accept(clone));
661: return clone;
662: }
663:
664: private void registerClone(Object clone, Object original, Descriptor descriptor) {
665: cloneToOriginals.put(clone, original);
666: final Object identifier = EntityPropertiesUtils.getIdentifier(clone, getMetamodel());
667: keysToClones.put(identifier, clone);
668: registerEntityWithPersistenceContext(clone);
669: registerEntityWithOntologyContext(descriptor, clone);
670: }
671:
672: /**
673: * Release this Unit of Work. Releasing an active Unit of Work with uncommitted changes causes all pending changes
674: * to be discarded.
675: */
676: @Override
677: public void release() {
678: clear();
679: storage.close();
680: this.isActive = false;
681: LOG.debug("UnitOfWork released.");
682: }
683:
684: @Override
685: public <T> void refreshObject(T object) {
686: Objects.requireNonNull(object);
687: if (!isObjectManaged(object)) {
688: throw new IllegalArgumentException(
689: "Cannot call refresh on an instance not managed by this persistence context.");
690: }
691: final EntityTypeImpl<T> et = entityType((Class<T>) object.getClass());
692: final URI idUri = EntityPropertiesUtils.getIdentifier(object, et);
693: final Descriptor descriptor = getDescriptor(object);
694: assert descriptor != null;
695:
696: final LoadingParameters<T> params = new LoadingParameters<>(et.getJavaType(), idUri, descriptor, true);
697: params.bypassCache();
698: final ConnectionWrapper connection = acquireConnection();
699: try {
700: uowChangeSet.cancelObjectChanges(getOriginal(object));
701: T original = connection.find(params);
702: if (original == null) {
703: throw new EntityNotFoundException("Entity " + object + " no longer exists in the repository.");
704: }
705: T source = (T) cloneBuilder.buildClone(original, new CloneConfiguration(descriptor));
706: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(source, object, descriptor);
707: changeManager.calculateChanges(chSet);
708: new RefreshInstanceMerger(collectionFactory).mergeChanges(chSet);
709: revertTransactionalChanges(object, descriptor, chSet);
710: registerClone(object, original, descriptor);
711: et.getLifecycleListenerManager().invokePostLoadCallbacks(object);
712: } finally {
713: connection.close();
714: }
715: }
716:
717: private <T> void revertTransactionalChanges(T object, Descriptor descriptor, ObjectChangeSet chSet) {
718: for (ChangeRecord change : chSet.getChanges()) {
719: storage.merge(object, change.getAttribute().getJavaField(),
720: descriptor.getAttributeDescriptor(change.getAttribute()));
721: }
722: }
723:
724: @Override
725: public void registerNewObject(Object entity, Descriptor descriptor) {
726: Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity"));
727: Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor"));
728:
729: registerNewObjectInternal(entity, descriptor);
730: }
731:
732: /**
733: * Registers the specified entity for persist in this Unit of Work.
734: *
735: * @param entity The entity to register
736: * @param descriptor Entity descriptor, specifying optionally contexts into which the entity will be persisted
737: */
738: private void registerNewObjectInternal(Object entity, Descriptor descriptor) {
739: final EntityTypeImpl<?> eType = entityType(entity.getClass());
740: eType.getLifecycleListenerManager().invokePrePersistCallbacks(entity);
741: Object id = getIdentifier(entity);
742: if (id == null) {
743: EntityPropertiesUtils.verifyIdentifierIsGenerated(entity, eType);
744: }
745: verifyCanPersist(id, entity, eType, descriptor);
746: storage.persist(id, entity, descriptor);
747: if (id == null) {
748: // If the ID was null, extract it from the entity. It is present now
749: id = getIdentifier(entity);
750: }
751: assert id != null;
752: // Original is null until commit
753: getNewObjectsCloneToOriginal().put(entity, null);
754: registerEntityWithPersistenceContext(entity);
755: registerEntityWithOntologyContext(descriptor, entity);
756: newObjectsKeyToClone.put(id, entity);
757: checkForCollections(entity);
758: this.hasNew = true;
759: eType.getLifecycleListenerManager().invokePostPersistCallbacks(entity);
760: }
761:
762: private void verifyCanPersist(Object id, Object instance, EntityType<?> et, Descriptor descriptor) {
763: if (isIndividualManaged(id, instance) && !instance.getClass().isEnum()) {
764: throw individualAlreadyManaged(id);
765: }
766: if (storage.contains(id, instance.getClass(), descriptor)) {
767: throw new OWLEntityExistsException(
768: "Individual " + id + " of type " + et.getIRI() + " already exists in storage.");
769: }
770: }
771:
772: private boolean isIndividualManaged(Object identifier, Object entity) {
773: // Allows persisting the same individual into different contexts
774: return keysToClones.containsKey(identifier) ||
775: newObjectsKeyToClone.containsKey(identifier) && !cloneMapping.contains(entity);
776: }
777:
778: @Override
779: public void removeObject(Object entity) {
780: assert entity != null;
781: if (!isObjectManaged(entity)) {
782: throw new IllegalArgumentException(
783: "Cannot remove entity which is not managed in the current persistence context.");
784: }
785: final EntityTypeImpl<?> et = entityType(entity.getClass());
786: et.getLifecycleListenerManager().invokePreRemoveCallbacks(entity);
787: final Object primaryKey = getIdentifier(entity);
788: final Descriptor descriptor = getDescriptor(entity);
789:
790: if (hasNew && getNewObjectsCloneToOriginal().containsKey(entity)) {
791: unregisterObject(entity);
792: newObjectsKeyToClone.remove(primaryKey);
793: } else {
794: getDeletedObjects().put(entity, entity);
795: this.hasDeleted = true;
796: }
797: storage.remove(primaryKey, et.getJavaType(), descriptor);
798: et.getLifecycleListenerManager().invokePostRemoveCallbacks(entity);
799: }
800:
801: @Override
802: public void restoreRemovedObject(Object entity) {
803: assert deletedObjects.containsKey(entity);
804:
805: deletedObjects.remove(entity);
806: final Object id = getIdentifier(entity);
807: storage.persist(id, entity, getDescriptor(entity));
808:
809: }
810:
811: /**
812: * Remove the registered object from this Unit of Work.
813: *
814: * @param object Clone of the original object
815: */
816: public void unregisterObject(Object object) {
817: if (object == null) {
818: return;
819: }
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 isEntityType(Class<?> cls) {
874: return parent.isEntityType(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 (isEntityType(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: }