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 			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 }