1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 package org.openrdf.elmo.sesame.behaviours;
30
31 import java.beans.PropertyChangeEvent;
32 import java.beans.PropertyChangeListener;
33 import java.beans.PropertyChangeSupport;
34 import java.lang.reflect.Constructor;
35 import java.lang.reflect.InvocationHandler;
36 import java.lang.reflect.Method;
37 import java.lang.reflect.Proxy;
38 import java.util.Arrays;
39 import java.util.HashSet;
40 import java.util.Iterator;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.WeakHashMap;
44 import java.util.concurrent.ConcurrentHashMap;
45 import java.util.concurrent.ConcurrentMap;
46
47 import javax.interceptor.InvocationContext;
48
49 import org.openrdf.elmo.Entity;
50 import org.openrdf.elmo.annotations.intercepts;
51 import org.openrdf.elmo.exceptions.ElmoIOException;
52 import org.openrdf.elmo.sesame.SesameManager;
53 import org.openrdf.elmo.sesame.roles.PropertyChangeNotifier;
54 import org.openrdf.elmo.sesame.roles.SesameEntity;
55 import org.openrdf.model.Resource;
56 import org.openrdf.repository.DelegatingRepositoryConnection;
57 import org.openrdf.repository.Repository;
58 import org.openrdf.repository.RepositoryConnection;
59 import org.openrdf.repository.RepositoryException;
60 import org.openrdf.repository.contextaware.ContextAwareConnection;
61 import org.openrdf.repository.event.NotifyingRepositoryConnection;
62 import org.openrdf.repository.event.base.NotifyingRepositoryConnectionWrapper;
63 import org.openrdf.repository.event.base.RepositoryConnectionListenerAdapter;
64
65
66
67
68
69
70
71
72
73 @SuppressWarnings("unchecked")
74 public class PropertyChangeNotifierSupport implements PropertyChangeNotifier {
75 static Set<String> triggers = new HashSet<String>(Arrays.asList("add",
76 "addAll", "clear", "remove", "removeAll", "retainAll"));
77
78 static Constructor<?> proxySet;
79
80 static Constructor<?> proxyIterator;
81 static {
82 try {
83 ClassLoader cl = PropertyChangeNotifierSupport.class
84 .getClassLoader();
85 if (cl == null)
86 cl = Thread.currentThread().getContextClassLoader();
87 Class[] invoClass = new Class[] { InvocationHandler.class };
88 Class[] setClass = new Class[] { Set.class };
89 Class[] iterClass = new Class[] { Iterator.class };
90 Class<?> proxySetClass = Proxy.getProxyClass(cl, setClass);
91 Class<?> proxyIterClass = Proxy.getProxyClass(cl, iterClass);
92 proxySet = proxySetClass.getConstructor(invoClass);
93 proxyIterator = proxyIterClass.getConstructor(invoClass);
94 } catch (NoSuchMethodException e) {
95 throw new AssertionError(e);
96 }
97 }
98
99 private final class ConnectionListener extends
100 RepositoryConnectionListenerAdapter {
101 private NotifyingRepositoryConnection broadcaster;
102
103 public ConnectionListener(RepositoryConnection conn) {
104 broadcaster = findBroadcaster(conn);
105 broadcaster.addRepositoryConnectionListener(this);
106 }
107
108 @Override
109 public void close(RepositoryConnection conn) {
110 firePropertyChange(null, null);
111 broadcaster.removeRepositoryConnectionListener(this);
112 }
113
114 @Override
115 public void setAutoCommit(RepositoryConnection conn, boolean autoCommit) {
116 firePropertyChange(null, null);
117 broadcaster.removeRepositoryConnectionListener(this);
118 }
119
120 @Override
121 public void commit(RepositoryConnection conn) {
122 firePropertyChange(null, null);
123 broadcaster.removeRepositoryConnectionListener(this);
124 }
125
126 @Override
127 public void rollback(RepositoryConnection conn) {
128 firePropertyChange(null, null);
129 broadcaster.removeRepositoryConnectionListener(this);
130 }
131 }
132
133 private class ModificationHandler implements InvocationHandler {
134 private Object delegate;
135
136 public ModificationHandler(Object delegate) {
137 this.delegate = delegate;
138 }
139
140 public Object invoke(Object proxy, Method method, Object[] args)
141 throws Throwable {
142 ++stack;
143 try {
144 Object result = method.invoke(delegate, args);
145 if (triggers.contains(method.getName()))
146 fireEvent(null, null);
147 if (method.getName().equals("iterator")) {
148 ModificationHandler handler = new ModificationHandler(result);
149 return proxyIterator.newInstance(new Object[] { handler });
150 }
151 return result;
152 } finally {
153 --stack;
154 }
155 }
156 }
157
158 private static Map<Repository, ConcurrentMap<Resource, PropertyChangeSupport>> managers = new WeakHashMap();
159
160 public static void notifyAllListenersOf(SesameManager manager) {
161 Repository repo = manager.getConnection().getRepository();
162 Map<Resource, PropertyChangeSupport> map = managers.get(repo);
163 if (map != null) {
164 for (Resource resource : map.keySet()) {
165 manager.refresh(manager.find(resource));
166 }
167 }
168 }
169
170 private ConcurrentMap<Resource, PropertyChangeSupport> beans;
171
172 private boolean immediate;
173
174 private Resource resource;
175
176 private SesameEntity bean;
177
178 private ConnectionListener listener;
179
180 int stack;
181
182 public PropertyChangeNotifierSupport(Entity elmo) {
183 this.bean = (SesameEntity) elmo;
184 this.resource = bean.getSesameResource();
185 SesameManager manager = bean.getSesameManager();
186 RepositoryConnection conn = manager.getConnection();
187 immediate = findBroadcaster(conn) == null;
188 Repository repository = conn.getRepository();
189 synchronized (managers) {
190 if (managers.containsKey(repository)) {
191 beans = managers.get(repository);
192 } else {
193 managers.put(repository, beans = new ConcurrentHashMap());
194 }
195 }
196 }
197
198 @intercepts(method = "get.*", parameters = {}, returns = Set.class)
199 public Object getCalled(InvocationContext ctx) throws Exception {
200 Object result = ctx.proceed();
201 ModificationHandler handler = new ModificationHandler(result);
202 return proxySet.newInstance(new Object[] { handler });
203 }
204
205 @intercepts(method = "set.*", argc = 1, returns = Void.class)
206 public Object setCalled(InvocationContext ctx) throws Exception {
207 ++stack;
208 try {
209 Object r = ctx.proceed();
210 fireEvent(ctx.getMethod().getName(), ctx.getParameters()[0]);
211 return r;
212 } finally {
213 --stack;
214 }
215 }
216
217 @intercepts(method = "merge", argc = 1, returns = Void.class)
218 public Object mergeCalled(InvocationContext ctx) throws Exception {
219 ++stack;
220 try {
221 Object r = ctx.proceed();
222 fireEvent(null, null);
223 return r;
224 } finally {
225 --stack;
226 }
227 }
228
229 @intercepts(method = "refresh", argc = 0, returns = Void.class)
230 public Object refreshCalled(InvocationContext ctx) throws Exception {
231 Object r = ctx.proceed();
232 if (stack == 0) {
233 fireEvent(null, null);
234 }
235 return r;
236 }
237
238 public void addPropertyChangeListener(PropertyChangeListener listener) {
239 PropertyChangeSupport subscribers;
240 if (beans.containsKey(resource)) {
241 subscribers = beans.get(resource);
242 } else {
243 subscribers = new PropertyChangeSupport(resource);
244 PropertyChangeSupport o = beans.putIfAbsent(resource, subscribers);
245 if (o != null) {
246 subscribers = o;
247 }
248 }
249 subscribers.addPropertyChangeListener(listener);
250 }
251
252 public void removePropertyChangeListener(PropertyChangeListener listener) {
253 if (beans.containsKey(resource)) {
254 PropertyChangeSupport subscribers = beans.get(resource);
255 subscribers.removePropertyChangeListener(listener);
256 }
257 }
258
259 void fireEvent(String setter, Object newValue) {
260 if (immediate) {
261 firePropertyChange(setter, newValue);
262 return;
263 }
264 SesameManager manager = bean.getSesameManager();
265 ContextAwareConnection conn = manager.getConnection();
266 try {
267 if (conn.isAutoCommit()) {
268 firePropertyChange(setter, newValue);
269 return;
270 }
271 } catch (RepositoryException e) {
272 throw new ElmoIOException(e);
273 }
274
275 if (listener == null)
276 listener = new ConnectionListener(conn);
277 }
278
279 void firePropertyChange(String setter, Object newValue) {
280 listener = null;
281 if (beans.containsKey(resource)) {
282 String property = null;
283 if (setter != null) {
284 char c = Character.toLowerCase(setter.charAt(3));
285 property = c + setter.substring(4);
286 }
287 PropertyChangeSupport subscribers = beans.get(resource);
288 PropertyChangeEvent evt = new PropertyChangeEvent(bean, property,
289 null, newValue);
290 subscribers.firePropertyChange(evt);
291 }
292 }
293
294 NotifyingRepositoryConnection findBroadcaster(RepositoryConnection conn) {
295 try {
296 if (conn instanceof NotifyingRepositoryConnectionWrapper) {
297 return (NotifyingRepositoryConnection) conn;
298 } else if (conn instanceof DelegatingRepositoryConnection) {
299 DelegatingRepositoryConnection dconn = (DelegatingRepositoryConnection) conn;
300 return findBroadcaster(dconn.getDelegate());
301 } else {
302 return null;
303 }
304 } catch (RepositoryException e) {
305 throw new AssertionError(e);
306 }
307 }
308 }