Skip to content

Method: calculateNewObjects(UnitOfWorkChangeSet)

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