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 Object result = method.invoke(delegate, args);
143 if (triggers.contains(method.getName()))
144 fireEvent(null, null);
145 if (method.getName().equals("iterator")) {
146 ModificationHandler handler = new ModificationHandler(result);
147 return proxyIterator.newInstance(new Object[] { handler });
148 }
149 return result;
150 }
151 }
152
153 private static Map<Repository, ConcurrentMap<Resource, PropertyChangeSupport>> managers = new WeakHashMap();
154
155 private ConcurrentMap<Resource, PropertyChangeSupport> beans;
156
157 private boolean immediate;
158
159 private Resource resource;
160
161 private SesameEntity bean;
162
163 private ConnectionListener listener;
164
165 public PropertyChangeNotifierSupport(Entity elmo) {
166 this.bean = (SesameEntity) elmo;
167 this.resource = bean.getSesameResource();
168 SesameManager manager = bean.getSesameManager();
169 RepositoryConnection conn = manager.getConnection();
170 immediate = findBroadcaster(conn) == null;
171 Repository repository = conn.getRepository();
172 synchronized (managers) {
173 if (managers.containsKey(repository)) {
174 beans = managers.get(repository);
175 } else {
176 managers.put(repository, beans = new ConcurrentHashMap());
177 }
178 }
179 }
180
181 @intercepts(method = "get.*", parameters = {}, returns = Set.class)
182 public Object getCalled(InvocationContext ctx) throws Exception {
183 Object result = ctx.proceed();
184 ModificationHandler handler = new ModificationHandler(result);
185 return proxySet.newInstance(new Object[] { handler });
186 }
187
188 @intercepts(method = "set.*", argc = 1, returns = Void.class)
189 public Object setCalled(InvocationContext ctx) throws Exception {
190 Object r = ctx.proceed();
191 fireEvent(ctx.getMethod().getName(), ctx.getParameters()[0]);
192 return r;
193 }
194
195 public void addPropertyChangeListener(PropertyChangeListener listener) {
196 PropertyChangeSupport subscribers;
197 if (beans.containsKey(resource)) {
198 subscribers = beans.get(resource);
199 } else {
200 subscribers = new PropertyChangeSupport(resource);
201 PropertyChangeSupport o = beans.putIfAbsent(resource, subscribers);
202 if (o != null) {
203 subscribers = o;
204 }
205 }
206 subscribers.addPropertyChangeListener(listener);
207 }
208
209 public void removePropertyChangeListener(PropertyChangeListener listener) {
210 if (beans.containsKey(resource)) {
211 PropertyChangeSupport subscribers = beans.get(resource);
212 subscribers.removePropertyChangeListener(listener);
213 }
214 }
215
216 void fireEvent(String setter, Object newValue) {
217 if (immediate) {
218 firePropertyChange(setter, newValue);
219 return;
220 }
221 SesameManager manager = bean.getSesameManager();
222 ContextAwareConnection conn = manager.getConnection();
223 try {
224 if (conn.isAutoCommit()) {
225 firePropertyChange(setter, newValue);
226 return;
227 }
228 } catch (RepositoryException e) {
229 throw new ElmoIOException(e);
230 }
231
232 if (listener == null)
233 listener = new ConnectionListener(conn);
234 }
235
236 void firePropertyChange(String setter, Object newValue) {
237 listener = null;
238 if (beans.containsKey(resource)) {
239 String property = null;
240 if (setter != null) {
241 char c = Character.toLowerCase(setter.charAt(3));
242 property = c + setter.substring(4);
243 }
244 PropertyChangeSupport subscribers = beans.get(resource);
245 PropertyChangeEvent evt = new PropertyChangeEvent(bean, property,
246 null, newValue);
247 subscribers.firePropertyChange(evt);
248 }
249 }
250
251 NotifyingRepositoryConnection findBroadcaster(RepositoryConnection conn) {
252 try {
253 if (conn instanceof NotifyingRepositoryConnectionWrapper) {
254 return (NotifyingRepositoryConnection) conn;
255 } else if (conn instanceof DelegatingRepositoryConnection) {
256 DelegatingRepositoryConnection dconn = (DelegatingRepositoryConnection) conn;
257 return findBroadcaster(dconn.getDelegate());
258 } else {
259 return null;
260 }
261 } catch (RepositoryException e) {
262 throw new AssertionError(e);
263 }
264 }
265 }