View Javadoc

1   /*
2    * Copyright (c) 2007, James Leigh All rights reserved.
3    * 
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    * 
7    * - Redistributions of source code must retain the above copyright notice, this
8    *   list of conditions and the following disclaimer.
9    * - Redistributions in binary form must reproduce the above copyright notice,
10   *   this list of conditions and the following disclaimer in the documentation
11   *   and/or other materials provided with the distribution. 
12   * - Neither the name of the openrdf.org nor the names of its contributors may
13   *   be used to endorse or promote products derived from this software without
14   *   specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26   * POSSIBILITY OF SUCH DAMAGE.
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   * Keeps a (non-persistent) list of observers, shared by all beans with the same
67   * subject. A strong reference is used for the subscribers and this does prevent
68   * garbage collection.
69   * 
70   * @author James Leigh
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 }