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