Wiki source code of EOF-Using EOF-Delegates and Notifications
Last modified by Pascal Robert on 2007/09/03 13:57
Show last authors
author | version | line-number | content |
---|---|---|---|
1 | == EO Change Notifications == | ||
2 | |||
3 | === Pierre Bernard === | ||
4 | |||
5 | To invalidate cached versions of derived values, you might want to use this: | ||
6 | |||
7 | {{code}} | ||
8 | |||
9 | /** Utility class. Provides for a way to watch for changes in EOs. The main use of this would be to | ||
10 | * be able to safely keep cached/computed/derived values that get cleared once out of sync. | ||
11 | * | ||
12 | * @author bernard | ||
13 | * @version ChangeNotificationCenter.java,v 1.1 2003/03/11 16:26:59 bernard Exp | ||
14 | **/ | ||
15 | public class ChangeNotificationCenter | ||
16 | { | ||
17 | // Public class constants | ||
18 | |||
19 | /** Name of the posted notification | ||
20 | */ | ||
21 | public static final String OBJECT_CHANGED = "MBSObjectChanged"; | ||
22 | |||
23 | /** Possible type of change a notification is posted for | ||
24 | */ | ||
25 | public static final String INVALIDATION = "Invalidation"; | ||
26 | |||
27 | /** Possible type of change a notification is posted for | ||
28 | */ | ||
29 | public static final String DELETION = "Deletion"; | ||
30 | |||
31 | /** Possible type of change a notification is posted for | ||
32 | */ | ||
33 | public static final String UPDATE = "Update"; | ||
34 | |||
35 | // Private class constants | ||
36 | |||
37 | /** Key in the posted notification's userInfo dictionary. | ||
38 | */ | ||
39 | private static final String OBJECT = "MBSObject"; | ||
40 | |||
41 | /** Key in the posted notification's userInfo dictionary. | ||
42 | */ | ||
43 | private static final String TYPE = "MBSType"; | ||
44 | |||
45 | /** Selector used for calling the watcher object upon a change | ||
46 | */ | ||
47 | private static final NSSelector watchedObjectChangedSelector = | ||
48 | new NSSelector("watchedObjectChanged", new Class[] { NSNotification.class }); | ||
49 | |||
50 | // Private class variables | ||
51 | |||
52 | /** Reference holding the shared singleton instance. | ||
53 | */ | ||
54 | private static ChangeNotificationCenter defaultCenter = null; | ||
55 | |||
56 | // Constructor | ||
57 | |||
58 | /** Singleton class | ||
59 | */ | ||
60 | private ChangeNotificationCenter() | ||
61 | { | ||
62 | super(); | ||
63 | |||
64 | Class[] notificationArray = new Class[] { NSNotification.class }; | ||
65 | NSSelector objectsChangedIInEditingContextSelector = | ||
66 | new NSSelector("objectsChangedInEditingContext", notificationArray); | ||
67 | |||
68 | NSNotificationCenter.defaultCenter().addObserver( | ||
69 | this, | ||
70 | objectsChangedIInEditingContextSelector, | ||
71 | EOEditingContext.ObjectsChangedInEditingContextNotification, | ||
72 | null); | ||
73 | } | ||
74 | |||
75 | // Public insatnce methods | ||
76 | |||
77 | /** Method called when a change notification is received. | ||
78 | * | ||
79 | * The notification is split and redispatched for all updated objects. | ||
80 | */ | ||
81 | public void objectsChangedInEditingContext(NSNotification notification) | ||
82 | { | ||
83 | NSArray updated = (NSArray) notification.userInfo().objectForKey(EOObjectStore.UpdatedKey); | ||
84 | |||
85 | if (updated != null) | ||
86 | { | ||
87 | int count = updated.count(); | ||
88 | |||
89 | for (int i = 0; i < count; i++) | ||
90 | { | ||
91 | Object object = updated.objectAtIndex(i); | ||
92 | NSDictionary userInfo = | ||
93 | new NSDictionary(new Object[] { object, UPDATE }, new Object[] { OBJECT, TYPE }); | ||
94 | NSNotificationCenter.defaultCenter().postNotification(OBJECT_CHANGED, object, userInfo); | ||
95 | } | ||
96 | } | ||
97 | |||
98 | NSArray invalidated = (NSArray) notification.userInfo().objectForKey(EOObjectStore.InvalidatedKey); | ||
99 | |||
100 | if (invalidated != null) | ||
101 | { | ||
102 | int count = invalidated.count(); | ||
103 | |||
104 | for (int i = 0; i < count; i++) | ||
105 | { | ||
106 | Object object = invalidated.objectAtIndex(i); | ||
107 | NSDictionary userInfo = | ||
108 | new NSDictionary(new Object[] { object, INVALIDATION }, new Object[] { OBJECT, TYPE }); | ||
109 | NSNotificationCenter.defaultCenter().postNotification(OBJECT_CHANGED, object, userInfo); | ||
110 | } | ||
111 | } | ||
112 | |||
113 | NSArray deleted = (NSArray) notification.userInfo().objectForKey(EOObjectStore.DeletedKey); | ||
114 | |||
115 | if (deleted != null) | ||
116 | { | ||
117 | int count = deleted.count(); | ||
118 | |||
119 | for (int i = 0; i < count; i++) | ||
120 | { | ||
121 | Object object = deleted.objectAtIndex(i); | ||
122 | NSDictionary userInfo = new NSDictionary(new Object[] { object, DELETION }, new Object[] { OBJECT, TYPE }); | ||
123 | NSNotificationCenter.defaultCenter().postNotification(OBJECT_CHANGED, object, userInfo); | ||
124 | } | ||
125 | } | ||
126 | } | ||
127 | |||
128 | /** Method to be called when one creates a cached value that depends on the attributes of a given object. | ||
129 | * | ||
130 | * Upon registration the object is watched and the watchedObjectChanged() is called once it changes, thus providing an | ||
131 | * oppurtunity to invalidate the cached value. | ||
132 | * | ||
133 | * @param watcher the watching object which should unregister before disposing | ||
134 | * @param object the object to watch | ||
135 | * @see #unregisterCacheDependancy | ||
136 | * @see #unregisterCacheDependancies | ||
137 | */ | ||
138 | public void registerCacheDependancy(ObserverInterface watcher, EOEnterpriseObject object) | ||
139 | { | ||
140 | NSNotificationCenter.defaultCenter().addObserver(watcher, watchedObjectChangedSelector, OBJECT_CHANGED, object); | ||
141 | } | ||
142 | |||
143 | /** Method to be called when one abandons or clears a cached value that depended on attributes of a given object. | ||
144 | * | ||
145 | * @param watcher the watching object | ||
146 | * @param object the object to watch | ||
147 | * @see #registerCacheDependancy | ||
148 | */ | ||
149 | public void unregisterCacheDependancy(ObserverInterface watcher, Object object) | ||
150 | { | ||
151 | NSNotificationCenter.defaultCenter().removeObserver(watcher, OBJECT_CHANGED, object); | ||
152 | } | ||
153 | |||
154 | /** Unregisters all of the callers dependancies. To be used sparingly as it may break unknown but required | ||
155 | * dependancies. Usually called when disposing the calling object. | ||
156 | * | ||
157 | * @param watcher the watching object | ||
158 | * @see #registerCacheDependancy | ||
159 | * @see #unregisterCacheDependancy | ||
160 | */ | ||
161 | public void unregisterCacheDependancies(ObserverInterface watcher) | ||
162 | { | ||
163 | NSNotificationCenter.defaultCenter().removeObserver(watcher, OBJECT_CHANGED, null); | ||
164 | } | ||
165 | |||
166 | // Public class methods | ||
167 | |||
168 | /** Gets or lazily instantiates the shared instance of the ChangeNotificationCenter | ||
169 | * | ||
170 | * @return the unique instance | ||
171 | */ | ||
172 | public static ChangeNotificationCenter defaultCenter() | ||
173 | { | ||
174 | if (defaultCenter == null) | ||
175 | { | ||
176 | createDefaultCenter(); | ||
177 | } | ||
178 | |||
179 | return defaultCenter; | ||
180 | } | ||
181 | |||
182 | /** Utility method. Allows for retrieving the changed object from a notification | ||
183 | * that results of registering interest in a change. | ||
184 | * | ||
185 | * @param notification the received notification | ||
186 | * @return the object that triggered the notification | ||
187 | */ | ||
188 | public static EOEnterpriseObject objectFromNotification(NSNotification notification) | ||
189 | { | ||
190 | return (EOEnterpriseObject) notification.userInfo().objectForKey(OBJECT); | ||
191 | } | ||
192 | |||
193 | /** Utility method. Allows for retrieving the type of change that ocurred from a notification | ||
194 | * that results of registering interest in a change. | ||
195 | * | ||
196 | * @param notification the received notification | ||
197 | * @return the type as one of the class constants defined in ChangeNotificationCenter | ||
198 | */ | ||
199 | public static String typeFromNotification(NSNotification notification) | ||
200 | { | ||
201 | return (String) notification.userInfo().objectForKey(TYPE); | ||
202 | } | ||
203 | |||
204 | // Private class methods | ||
205 | |||
206 | /** Creates the singleton instance ensuring there is no existing instance. | ||
207 | */ | ||
208 | private static synchronized void createDefaultCenter() | ||
209 | { | ||
210 | if (defaultCenter == null) | ||
211 | { | ||
212 | defaultCenter = new ChangeNotificationCenter(); | ||
213 | } | ||
214 | } | ||
215 | |||
216 | // Inner interface definition | ||
217 | |||
218 | /** Interface to be implemented by objects that need to listen to changes. | ||
219 | * | ||
220 | * CAVEAT: in most cases it is recommended to not directly implement the interface, | ||
221 | * but rather create an inner class that implements the interface or better yet | ||
222 | * extends the default implementation. Indeed if an object (e.g. an EO) which | ||
223 | * participates in an inheritance hierarchy is used as receiver for notifications, | ||
224 | * registering or unregistering might break functionality in a parent class. | ||
225 | */ | ||
226 | public static interface ObserverInterface | ||
227 | { | ||
228 | /** Method called when an object on which locally cached values depend is modified. | ||
229 | * | ||
230 | * This hook is provided as an opportunity to clear locally cached values. It's a good idea not to recreate | ||
231 | * the cached values immediately, but on an as-needed basis. You should refrain from changing persisted EO | ||
232 | * attributes from within this method as this might kick off another chain of notifications. | ||
233 | * | ||
234 | * Once the caches cleared, it would be a very good idea to unregister from further notifications until | ||
235 | * the cache is recreated. | ||
236 | * | ||
237 | * @see lu.bcl.enterprise.entity.ChangeNotificationCenter#objectFromNotification | ||
238 | * @see lu.bcl.enterprise.entity.ChangeNotificationCenter#registerCacheDependancy | ||
239 | * @see lu.bcl.enterprise.entity.ChangeNotificationCenter#unRegisterCacheDependancy | ||
240 | */ | ||
241 | public void watchedObjectChanged(NSNotification notification); | ||
242 | } | ||
243 | |||
244 | // Inner class | ||
245 | |||
246 | /** Convenience class for implementing ObserverInterface. | ||
247 | * | ||
248 | * The recommended way of registering for change notificications is to create an inner | ||
249 | * class extending this one. | ||
250 | */ | ||
251 | public abstract static class Observer implements ObserverInterface | ||
252 | { | ||
253 | /** Method to be called by subclasses when the create a cached value that depends on the attributes of a given object. | ||
254 | * | ||
255 | * Upon registration the object is watched and the clearCachedValues() is called once it changes, thus providing an | ||
256 | * oppurtunity to invalidate the cached value. | ||
257 | * | ||
258 | * You need to register for each object your locally cached values depend on. E.g. if you build a cached value from | ||
259 | * attributes of EOs in a too-many relationship, your cache depends on each of the objects in the relationship as well | ||
260 | * as on the source of the realtionship. | ||
261 | * | ||
262 | * N.B. Extend the dispose() method to unregister any dependancy you might be registered for. | ||
263 | * | ||
264 | * @param object the object to watch | ||
265 | * @see #unregisterCacheDependancy | ||
266 | * @see #clearCachedValues | ||
267 | */ | ||
268 | protected void registerCacheDependancy(EOEnterpriseObject object) | ||
269 | { | ||
270 | ChangeNotificationCenter.defaultCenter().registerCacheDependancy(this, object); | ||
271 | } | ||
272 | |||
273 | /** Method to be called by subclasses when they abandon or clear a cached value that depended on attributes | ||
274 | * of a given object. | ||
275 | * | ||
276 | * @param object the object to watch | ||
277 | * @see #registerCacheDependancy | ||
278 | * @see #clearCachedValues | ||
279 | */ | ||
280 | protected void unregisterCacheDependancy(EOEnterpriseObject object) | ||
281 | { | ||
282 | ChangeNotificationCenter.defaultCenter().unregisterCacheDependancy(this, object); | ||
283 | } | ||
284 | |||
285 | /** Unregisters all of the callers dependancies. To be used sparingly as it may break unknown but required | ||
286 | * dependancies. Usually called when disposing the calling object. | ||
287 | * | ||
288 | * @see #registerCacheDependancy | ||
289 | * @see #unregisterCacheDependancy | ||
290 | */ | ||
291 | public void unregisterCacheDependancies() | ||
292 | { | ||
293 | ChangeNotificationCenter.defaultCenter().unregisterCacheDependancies(this); | ||
294 | } | ||
295 | |||
296 | /** Overridden to unregister all cache dependancies | ||
297 | */ | ||
298 | public void finalize() throws Throwable | ||
299 | { | ||
300 | ChangeNotificationCenter.defaultCenter().unregisterCacheDependancies(this); | ||
301 | |||
302 | super.finalize(); | ||
303 | } | ||
304 | } | ||
305 | } | ||
306 | |||
307 | {{/code}} |