Skip to contentMethod: lambda$detachAllManagedInstances$0(Object)
1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.sessions;
19:
20: import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException;
21: import cz.cvut.kbss.jopa.model.LoadState;
22: import cz.cvut.kbss.jopa.model.Manageable;
23: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
24: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
25: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
26: import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType;
27: import cz.cvut.kbss.jopa.proxy.IndirectWrapper;
28: import cz.cvut.kbss.jopa.proxy.lazy.LazyLoadingProxy;
29: import cz.cvut.kbss.jopa.sessions.change.ChangeRecord;
30: import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory;
31: import cz.cvut.kbss.jopa.sessions.change.ObjectChangeSet;
32: import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor;
33: import cz.cvut.kbss.jopa.sessions.validator.AttributeModificationValidator;
34: import cz.cvut.kbss.jopa.utils.Configuration;
35: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
36:
37: import java.lang.reflect.Field;
38: import java.net.URI;
39:
40: public class ChangeTrackingUnitOfWork extends AbstractUnitOfWork {
41:
42: public ChangeTrackingUnitOfWork(AbstractSession parent, Configuration configuration) {
43: super(parent, configuration);
44: }
45:
46: @Override
47: protected <T> T readObjectInternal(Class<T> cls, Object identifier, Descriptor descriptor) {
48: final T clone = super.readObjectInternal(cls, identifier, descriptor);
49: if (clone != null) {
50: checkForIndirectObjects(clone);
51: }
52: return clone;
53: }
54:
55: /**
56: * Check if the specified entity contains a collection. If so, replace it with its indirect representation so that
57: * changes in that collection can be tracked.
58: *
59: * @param entity The entity to check
60: */
61: private void checkForIndirectObjects(Object entity) {
62: assert entity != null;
63: final EntityType<?> et = entityType(entity.getClass());
64: for (FieldSpecification<?, ?> fieldSpec : et.getFieldSpecifications()) {
65: setIndirectObjectIfPresent(entity, fieldSpec.getJavaField());
66: }
67: }
68:
69: /**
70: * Create and set indirect collection on the specified entity field.
71: * <p>
72: * If the specified field is of Collection type, and it is not already an indirect collection, create new one and
73: * set it as the value of the specified field on the specified entity.
74: *
75: * @param entity The entity collection will be set on
76: * @param field The field to set
77: * @throws IllegalArgumentException Reflection
78: */
79: private void setIndirectObjectIfPresent(Object entity, Field field) {
80: assert entity != null;
81: assert field != null;
82:
83: final Object value = EntityPropertiesUtils.getFieldValue(field, entity);
84: if (value instanceof IndirectWrapper) {
85: return;
86: }
87: if (IndirectWrapperHelper.requiresIndirectWrapper(value)) {
88: EntityPropertiesUtils.setFieldValue(field, entity, indirectWrapperHelper.createIndirectWrapper(value, entity, field));
89: }
90: }
91:
92: /**
93: * Creates an indirect collection, which wraps the specified collection instance and propagates changes to the
94: * persistence context.
95: *
96: * @param collection Collection to be proxied
97: * @param owner Collection owner instance
98: * @param field Field filled with the collection
99: * @return Indirect collection
100: */
101: public Object createIndirectCollection(Object collection, Object owner, Field field) {
102: return indirectWrapperHelper.createIndirectWrapper(collection, owner, field);
103: }
104:
105: /**
106: * If there are any changes, commit them to the ontology.
107: */
108: void commitToStorage() {
109: if (this.hasNew || this.hasChanges || this.hasDeleted) {
110: persistNewObjects();
111: calculateChanges();
112: }
113: validateIntegrityConstraints();
114: storage.commit();
115: }
116:
117: @Override
118: protected void detachAllManagedInstances() {
119: cloneMapping.forEach(instance -> {
120: removeIndirectWrappersAndProxies(instance);
121: deregisterEntityFromPersistenceContext(instance);
122: });
123: newObjectsCloneToOriginal.keySet().forEach(this::removeIndirectWrappersAndProxies);
124: }
125:
126: /**
127: * Removes {@link IndirectWrapper} and {@link LazyLoadingProxy} instances from the specified entity (if present).
128: *
129: * @param entity The entity to remove indirect wrappers from
130: */
131: private void removeIndirectWrappersAndProxies(Object entity) {
132: assert entity != null;
133: final EntityType<?> et = entityType(entity.getClass());
134: for (FieldSpecification<?, ?> fs : et.getFieldSpecifications()) {
135: final Object value = EntityPropertiesUtils.getFieldValue(fs.getJavaField(), entity);
136: if (value instanceof IndirectWrapper indirectWrapper) {
137: EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, indirectWrapper.unwrap());
138: } else if (value instanceof LazyLoadingProxy lazyLoadingProxy) {
139: EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, lazyLoadingProxy.unwrap());
140: }
141: }
142: }
143:
144: @Override
145: void registerClone(Object clone, Object original, Descriptor descriptor) {
146: super.registerClone(clone, original, descriptor);
147: attachPersistenceContextToEntity(clone);
148: }
149:
150: private void attachPersistenceContextToEntity(Object entity) {
151: if (isFlushingChanges()) {
152: return;
153: }
154: assert entity instanceof Manageable;
155: ((Manageable) entity).setPersistenceContext(this);
156: }
157:
158: private static void deregisterEntityFromPersistenceContext(Object entity) {
159: if (!(entity instanceof Manageable)) {
160: return;
161: }
162: ((Manageable) entity).setPersistenceContext(null);
163: }
164:
165: @Override
166: public void attributeChanged(Object entity, Field f) {
167: final IdentifiableEntityType<Object> et = entityType((Class<Object>) entity.getClass());
168: final FieldSpecification<Object, ?> fieldSpec = et.getFieldSpecification(f.getName());
169: attributeChanged(entity, fieldSpec);
170: }
171:
172: @Override
173: public void attributeChanged(Object entity, FieldSpecification<?, ?> fieldSpec) {
174: if (!isInTransaction()) {
175: throw new IllegalStateException("This unit of work is not in a transaction.");
176: }
177: final Descriptor descriptor = getDescriptor(entity);
178: final IdentifiableEntityType<Object> et = entityType((Class<Object>) entity.getClass());
179: final Object original = getOriginal(entity);
180: if (fieldSpec.isInferred() && original != null) {
181: inferredAttributeChangeValidator.validateChange(entity, getOriginal(entity), (FieldSpecification<? super Object, ?>) fieldSpec, descriptor);
182: }
183: et.getLifecycleListenerManager().invokePreUpdateCallbacks(entity);
184: storage.merge(entity, (FieldSpecification<? super Object, ?>) fieldSpec, descriptor);
185: createAndRegisterChangeRecord(entity, fieldSpec, descriptor);
186: setHasChanges();
187: setIndirectObjectIfPresent(entity, fieldSpec.getJavaField());
188: et.getLifecycleListenerManager().invokePostUpdateCallbacks(entity);
189: ((LoadStateDescriptor) loadStateRegistry.get(entity)).setLoaded(fieldSpec, LoadState.LOADED);
190: }
191:
192: private void createAndRegisterChangeRecord(Object clone, FieldSpecification<?, ?> fieldSpec,
193: Descriptor descriptor) {
194: final Object orig = getOriginal(clone);
195: if (orig == null) {
196: return;
197: }
198: final ChangeRecord record = new ChangeRecord(fieldSpec, EntityPropertiesUtils.getFieldValue(fieldSpec.getJavaField(), clone));
199: preventCachingIfReferenceIsNotLoaded(record);
200: registerChangeRecord(clone, orig, descriptor, record);
201: }
202:
203: private void registerChangeRecord(Object clone, Object orig, Descriptor descriptor, ChangeRecord record) {
204: ObjectChangeSet chSet = uowChangeSet.getExistingObjectChanges(orig);
205: if (chSet == null) {
206: chSet = ChangeSetFactory.createObjectChangeSet(orig, clone, descriptor);
207: uowChangeSet.addObjectChangeSet(chSet);
208: }
209: chSet.addChangeRecord(record);
210: }
211:
212: <T> T mergeDetachedInternal(T entity, Descriptor descriptor) {
213: assert entity != null;
214: final IdentifiableEntityType<T> et = (IdentifiableEntityType<T>) entityType(entity.getClass());
215: final URI idUri = EntityPropertiesUtils.getIdentifier(entity, et);
216:
217: final T clone = getInstanceForMerge(idUri, et, descriptor);
218: try {
219: ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(clone, entity, descriptor);
220: // Merge only the changed attributes
221: changeCalculator.calculateChanges(chSet);
222: // Have to check for inferred attribute changes before the actual merge
223: chSet = processInferredValueChanges(chSet);
224: if (chSet.hasChanges()) {
225: et.getLifecycleListenerManager().invokePreUpdateCallbacks(clone);
226: final DetachedInstanceMerger merger = new DetachedInstanceMerger(this);
227: merger.mergeChangesFromDetachedToManagedInstance(chSet, descriptor);
228: for (ChangeRecord record : chSet.getChanges()) {
229: AttributeModificationValidator.verifyCanModify(record.getAttribute());
230: preventCachingIfReferenceIsNotLoaded(record);
231: storage.merge(clone, (FieldSpecification<? super T, ?>) record.getAttribute(), descriptor);
232: }
233: et.getLifecycleListenerManager().invokePostUpdateCallbacks(clone);
234: registerMergeChangeSet(chSet, clone, descriptor);
235: }
236: } catch (OWLEntityExistsException e) {
237: unregisterObject(clone);
238: throw e;
239: }
240: evictAfterMerge(et, idUri, descriptor);
241: setHasChanges();
242: checkForIndirectObjects(clone);
243: return et.getJavaType().cast(clone);
244: }
245:
246: private <T> void registerMergeChangeSet(ObjectChangeSet mergeChangeSet, T clone, Descriptor descriptor) {
247: final Object original = getOriginal(clone);
248: if (uowChangeSet.getExistingObjectChanges(original) != null) {
249: final ObjectChangeSet existingChSet = uowChangeSet.getExistingObjectChanges(original);
250: mergeChangeSet.getChanges().forEach(existingChSet::addChangeRecord);
251: } else {
252: uowChangeSet.addObjectChangeSet(copyChangeSet(mergeChangeSet, original, clone, descriptor));
253: }
254: }
255:
256: @Override
257: public void registerNewObject(Object entity, Descriptor descriptor) {
258: super.registerNewObject(entity, descriptor);
259: checkForIndirectObjects(entity);
260: }
261:
262: @Override
263: public void unregisterObject(Object object) {
264: super.unregisterObject(object);
265: removeIndirectWrappersAndProxies(object);
266: deregisterEntityFromPersistenceContext(object);
267: }
268:
269: @Override
270: public void removeObject(Object entity) {
271: assert entity != null;
272: ensureManaged(entity);
273:
274: final IdentifiableEntityType<?> et = entityType(entity.getClass());
275: et.getLifecycleListenerManager().invokePreRemoveCallbacks(entity);
276: final Object identifier = getIdentifier(entity);
277: // Get the descriptor before clone is removed
278: final Descriptor descriptor = getDescriptor(entity);
279:
280: markCloneForDeletion(entity, identifier);
281: storage.remove(identifier, et.getJavaType(), descriptor);
282: et.getLifecycleListenerManager().invokePostRemoveCallbacks(entity);
283: }
284: }