Skip to content

Method: invokePostUpdateCallbacks(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.model.metamodel;
19:
20: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
21: import cz.cvut.kbss.jopa.model.annotations.EntityListeners;
22: import cz.cvut.kbss.jopa.model.lifecycle.LifecycleEvent;
23:
24: import java.lang.reflect.InvocationTargetException;
25: import java.lang.reflect.Method;
26: import java.util.ArrayList;
27: import java.util.Collections;
28: import java.util.EnumMap;
29: import java.util.HashMap;
30: import java.util.HashSet;
31: import java.util.IdentityHashMap;
32: import java.util.List;
33: import java.util.Map;
34: import java.util.Optional;
35: import java.util.Set;
36:
37: /**
38: * Manages entity lifecycle callbacks declared either in the entity (entity lifecycle callbacks) or in its entity
39: * listener (entity listener callbacks) and provides means for their invocation.
40: */
41: public class EntityLifecycleListenerManager {
42:
43: private static final EntityLifecycleListenerManager EMPTY = new EntityLifecycleListenerManager();
44:
45: private final Set<EntityLifecycleListenerManager> parents = new HashSet<>();
46:
47: private final Map<LifecycleEvent, Method> lifecycleCallbacks = new EnumMap<>(LifecycleEvent.class);
48:
49: private List<Object> entityListeners;
50:
51: private Map<Object, Map<LifecycleEvent, Method>> entityListenerCallbacks;
52:
53: private final Map<Object, Object> instancesBeingProcessed = new IdentityHashMap<>();
54:
55: /**
56: * Gets default instance of this manager, which contains no listeners and does nothing on invocation.
57: *
58: * @return Default {@link EntityLifecycleListenerManager} instance
59: */
60: public static EntityLifecycleListenerManager empty() {
61: return EMPTY;
62: }
63:
64: /**
65: * Calls pre-persist callbacks for the specified instance.
66: * <p>
67: * These include:
68: * <ul>
69: * <li>Lifecycle callbacks declared by the entity or its managed ancestors,</li>
70: * <li>Callbacks declared in classes referenced by {@link EntityListeners} on the entity or its ancestors.</li>
71: * </ul>
72: *
73: * @param instance The instance for persist
74: */
75: public void invokePrePersistCallbacks(Object instance) {
76: invokeCallbacks(instance, LifecycleEvent.PRE_PERSIST);
77: }
78:
79: private void invokeCallbacks(Object instance, LifecycleEvent lifecycleEvent) {
80: // The manager may be invoked from multiple threads (as each entity type has its listener and entity types are global for PU)
81: synchronized (this) {
82: if (instancesBeingProcessed.containsKey(instance)) {
83: return;
84: }
85: instancesBeingProcessed.put(instance, EMPTY);
86: }
87: try {
88: invokeEntityListenerCallbacks(instance, lifecycleEvent);
89: invokeInternalCallbacks(instance, lifecycleEvent);
90: } finally {
91: synchronized (this) {
92: instancesBeingProcessed.remove(instance);
93: }
94: }
95: }
96:
97: private void invokeEntityListenerCallbacks(Object instance, LifecycleEvent lifecycleEvent) {
98:
99: parents.forEach(parent -> parent.invokeEntityListenerCallbacks(instance, lifecycleEvent));
100:
101: if (entityListeners != null) {
102: entityListeners
103: .forEach(listener -> getEntityListenerCallback(listener, lifecycleEvent).ifPresent(method -> {
104: if (!method.canAccess(listener)) {
105: method.setAccessible(true);
106: }
107: try {
108: method.invoke(listener, instance);
109: } catch (IllegalAccessException | InvocationTargetException e) {
110: throw new OWLPersistenceException("Unable to invoke entity listener method " + method, e);
111: }
112: }));
113: }
114: }
115:
116: private Optional<Method> getEntityListenerCallback(Object listener, LifecycleEvent lifecycleEvent) {
117: final Map<LifecycleEvent, Method> callbacks = entityListenerCallbacks.get(listener);
118: return Optional.ofNullable(callbacks.get(lifecycleEvent));
119: }
120:
121: private void invokeInternalCallbacks(Object instance, LifecycleEvent lifecycleEvent) {
122:
123: parents.forEach(parent -> parent.invokeInternalCallbacks(instance, lifecycleEvent));
124:
125: if (lifecycleCallbacks.containsKey(lifecycleEvent)) {
126: final Method listener = lifecycleCallbacks.get(lifecycleEvent);
127: if (!listener.canAccess(instance)) {
128: listener.setAccessible(true);
129: }
130: try {
131: listener.invoke(instance);
132: } catch (IllegalAccessException | InvocationTargetException e) {
133: throw new OWLPersistenceException("Unable to invoke method lifecycle listener " + listener, e);
134: }
135: }
136: }
137:
138: /**
139: * Calls post-persist callbacks for the specified instance.
140: * <p>
141: * These include:
142: * <ul>
143: * <li>Lifecycle callbacks declared by the entity or its managed ancestors,</li>
144: * <li>Callbacks declared in classes referenced by {@link EntityListeners} on the entity or its ancestors.</li>
145: * </ul>
146: *
147: * @param instance The newly persisted instance
148: */
149: public void invokePostPersistCallbacks(Object instance) {
150: invokeCallbacks(instance, LifecycleEvent.POST_PERSIST);
151: }
152:
153: /**
154: * Calls post-load callbacks for the specified instance.
155: * <p>
156: * These include:
157: * <ul>
158: * <li>Lifecycle callbacks declared by the entity or its managed ancestors,</li>
159: * <li>Callbacks declared in classes referenced by {@link EntityListeners} on the entity or its ancestors.</li>
160: * </ul>
161: *
162: * @param instance The loaded instance
163: */
164: public void invokePostLoadCallbacks(Object instance) {
165: invokeCallbacks(instance, LifecycleEvent.POST_LOAD);
166: }
167:
168: /**
169: * Calls pre-update callbacks for the specified instance.
170: * <p>
171: * These include:
172: * <ul>
173: * <li>Lifecycle callbacks declared by the entity or its managed ancestors,</li>
174: * <li>Callbacks declared in classes referenced by {@link EntityListeners} on the entity or its ancestors.</li>
175: * </ul>
176: *
177: * @param instance The updated instance
178: */
179: public void invokePreUpdateCallbacks(Object instance) {
180: invokeCallbacks(instance, LifecycleEvent.PRE_UPDATE);
181: }
182:
183: /**
184: * Calls post-update callbacks for the specified instance.
185: * <p>
186: * These include:
187: * <ul>
188: * <li>Lifecycle callbacks declared by the entity or its managed ancestors,</li>
189: * <li>Callbacks declared in classes referenced by {@link EntityListeners} on the entity or its ancestors.</li>
190: * </ul>
191: *
192: * @param instance The updated instance
193: */
194: public void invokePostUpdateCallbacks(Object instance) {
195: invokeCallbacks(instance, LifecycleEvent.POST_UPDATE);
196: }
197:
198: /**
199: * Calls pre-remove callbacks for the specified instance.
200: * <p>
201: * These include:
202: * <ul>
203: * <li>Lifecycle callbacks declared by the entity or its managed ancestors,</li>
204: * <li>Callbacks declared in classes referenced by {@link EntityListeners} on the entity or its ancestors.</li>
205: * </ul>
206: *
207: * @param instance The instance for removal
208: */
209: public void invokePreRemoveCallbacks(Object instance) {
210: invokeCallbacks(instance, LifecycleEvent.PRE_REMOVE);
211: }
212:
213: /**
214: * Calls post-remove callbacks for the specified instance.
215: * <p>
216: * These include:
217: * <ul>
218: * <li>Lifecycle callbacks declared by the entity or its managed ancestors,</li>
219: * <li>Callbacks declared in classes referenced by {@link EntityListeners} on the entity or its ancestors.</li>
220: * </ul>
221: *
222: * @param instance The removed instance
223: */
224: public void invokePostRemoveCallbacks(Object instance) {
225: invokeCallbacks(instance, LifecycleEvent.POST_REMOVE);
226: }
227:
228: void addParent(EntityLifecycleListenerManager parent) {
229: assert parent != null;
230: this.parents.add(parent);
231: }
232:
233: Set<EntityLifecycleListenerManager> getParents() {
234: return parents;
235: }
236:
237: void addEntityListener(Object entityListener) {
238: assert entityListener != null;
239:
240: if (entityListeners == null) {
241: this.entityListeners = new ArrayList<>();
242: }
243: entityListeners.add(entityListener);
244: if (entityListenerCallbacks == null) {
245: this.entityListenerCallbacks = new HashMap<>();
246: }
247: entityListenerCallbacks.put(entityListener, new EnumMap<>(LifecycleEvent.class));
248: }
249:
250: void addLifecycleCallback(LifecycleEvent event, Method callback) {
251: assert event != null;
252: assert callback != null;
253:
254: lifecycleCallbacks.put(event, callback);
255: }
256:
257: Map<LifecycleEvent, Method> getLifecycleCallbacks() {
258: return Collections.unmodifiableMap(lifecycleCallbacks);
259: }
260:
261: boolean hasEntityLifecycleCallback(LifecycleEvent event) {
262: return lifecycleCallbacks.containsKey(event) || parents.stream()
263: .anyMatch(parent -> parent.hasEntityLifecycleCallback(event));
264: }
265:
266: List<Object> getEntityListeners() {
267: final List<Object> allListeners = new ArrayList<>(parents.stream().flatMap(parent -> parent.getEntityListeners()
268: .stream()).toList());
269: if (entityListeners != null) {
270: allListeners.addAll(entityListeners);
271: }
272: return allListeners;
273: }
274:
275: Map<Object, Map<LifecycleEvent, Method>> getEntityListenerCallbacks() {
276: return entityListenerCallbacks != null ? Collections.unmodifiableMap(entityListenerCallbacks) :
277: Collections.emptyMap();
278: }
279:
280: void addEntityListenerCallback(Object listener, LifecycleEvent event, Method callback) {
281: assert listener != null;
282: assert event != null;
283: assert callback != null;
284: assert entityListenerCallbacks.containsKey(listener);
285:
286: entityListenerCallbacks.get(listener).put(event, callback);
287: }
288:
289: boolean hasEntityListenerCallback(Object listener, LifecycleEvent event) {
290: return entityListenerCallbacks != null && entityListenerCallbacks.containsKey(listener) &&
291: entityListenerCallbacks.get(listener).containsKey(event);
292: }
293:
294: /**
295: * Checks whether there is a lifecycle callback defined for the specified event.
296: * <p>
297: * This checks both callbacks declared in the entity class and in an entity listener class.
298: *
299: * @param event Lifecycle event to find callback for
300: * @return {@code true} if there is a matching callback, {@code false} otherwise
301: */
302: public boolean hasLifecycleCallback(LifecycleEvent event) {
303: return hasEntityLifecycleCallback(event) || getEntityListeners().stream()
304: .anyMatch(listener -> hasEntityListenerCallback(listener, event));
305: }
306: }