View Javadoc

1   /*
2    * The SmartConfig Library
3    * Copyright (C) 2004-2006
4    *
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18   *
19   * For further informations on the SmartConfig Library please visit
20   *
21   *                        http://smartconfig.sourceforge.net
22   */
23  package net.smartlab.config;
24  
25  import java.net.URI;
26  import java.net.URISyntaxException;
27  import java.sql.DriverManager;
28  import java.sql.SQLException;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.StringTokenizer;
35  
36  import org.xml.sax.Attributes;
37  
38  /**
39   * This class provides a skeletal implementation of a configuration tree to
40   * provide a set of common operations with a generic implementation.
41   * 
42   * @author rlogiacco
43   * @uml.dependency supplier="net.smartlab.config.ConfigurationException"
44   */
45  abstract public class Configuration extends Node {
46  
47  	/**
48  	 * @uml.property name="resolve"
49  	 */
50  	private boolean resolve;
51  
52  	/**
53  	 * @uml.property name="listeners"
54  	 */
55  	protected Collection listeners = new ArrayList();
56  
57  	/**
58  	 * @uml.property name="monitor"
59  	 */
60  	protected Monitor monitor = new Monitor();
61  
62  
63  	/**
64  	 * Creates an instance of the class as an <code>Element</code> without a
65  	 * parent node. This constructor is providen for subclasses convenience.
66  	 */
67  	protected Configuration() {
68  		super(null, null, null);
69  		this.monitor.start();
70  	}
71  
72  	/**
73  	 * Initializes this configuration with a name and a set of attributes.
74  	 * 
75  	 * @param name the name of the configuration tree.
76  	 * @param attributes a set of attributes relative to the global
77  	 *        configuration tree.
78  	 */
79  	protected void init(String name, Attributes attributes) {
80  		this.name = name;
81  		if (attributes != null) {
82  			this.attributes = new HashMap(attributes.getLength());
83  			for (int i = 0; i < attributes.getLength(); i++) {
84  				this.attributes.put(attributes.getQName(i), attributes.getValue(i));
85  			}
86  		}
87  		this.parent = this;
88  	}
89  
90  	/**
91  	 * @see net.smartlab.config.Node#resolve(java.lang.String, java.lang.String)
92  	 */
93  	protected Node resolve(String name, String id) throws ConfigurationException {
94  		Iterator children = this.children.iterator();
95  		while (children.hasNext()) {
96  			Element child = (Element)children.next();
97  			if (child instanceof Node && name.equals(child.name) && id.equals(child.getId())) {
98  				return (Node)child;
99  			}
100 		}
101 		throw new ConfigurationException("Cannot resolve element " + name + " with id " + id);
102 	}
103 
104 	/**
105 	 * This method checks if the source from the configuration structure was
106 	 * modified since it was last read.
107 	 * 
108 	 * @return <code>true</code> if the underlying representation of the
109 	 *         structure was modified since last read.
110 	 * @exception ConfigurationException if something wrong occurred while
111 	 *            checking the underlaying structure like the file is no more
112 	 *            readable or the stream no more available.
113 	 */
114 	public abstract boolean isChanged() throws ConfigurationException;
115 
116 	/**
117 	 * Ensures the in memory representation of the configuration matches with
118 	 * the underlying representation.
119 	 * 
120 	 * @exception ConfigurationException if the underlying representation of the
121 	 *            structure is no more accessible or the decoding process fails
122 	 *            due to an unappropriately initialized <code>Cipher</code>.
123 	 */
124 	public abstract void update() throws ConfigurationException;
125 
126 	/**
127 	 * Sets the resolution method to adopt while querying this configuration
128 	 * tree. When set to <code>true</code> all internal references are
129 	 * automatically resolved to the destination node if present in the
130 	 * configuration tree.
131 	 * 
132 	 * @param resolve the resolution method to adopt.
133 	 * @uml.property name="resolve"
134 	 */
135 	public void setResolve(boolean resolve) {
136 		this.resolve = resolve;
137 	}
138 
139 	/**
140 	 * Returns the resolution method adopted by this configuration tree.
141 	 * 
142 	 * @return <code>true</code> if internal references should be
143 	 *         automatically resolved.
144 	 * @uml.property name="resolve"
145 	 */
146 	public boolean getResolve() {
147 		return resolve;
148 	}
149 
150 	/**
151 	 * Returns the registered changes listeners.
152 	 * 
153 	 * @return the registered changes listeners.
154 	 * @uml.property name="listeners"
155 	 */
156 	public Collection getListeners() {
157 		return Collections.unmodifiableCollection(listeners);
158 	}
159 
160 	/**
161 	 * Adds a configuration changes listener.
162 	 * 
163 	 * @param listener the listener to add.
164 	 */
165 	public void addListener(Listener listener) {
166 		if (listener != null) {
167 			listeners.add(listener);
168 		}
169 	}
170 
171 	/**
172 	 * Removes a configuration changes listener.
173 	 * 
174 	 * @param listener the listener to remove.
175 	 */
176 	public void removeListener(Listener listener) {
177 		listeners.remove(listener);
178 	}
179 
180 	/**
181 	 * Returns an instance of a <code>Configuration</code> subclass
182 	 * accordingly to the provided URI.
183 	 * 
184 	 * Monitoring interval can be set through the system property
185 	 * <code>smartconfig.monitor.delay</code> expressed in milliseconds and
186 	 * defaulting to <b>60000</b>.
187 	 * 
188 	 * @param uri the URI to access the configuration.
189 	 * @return an instance of a <code>Configuration</code> subclass.
190 	 * @throws ConfigurationException if no handler matches the specified URI or
191 	 *         an error is encountered trying to access the configuration URI.
192 	 */
193 	public final static Configuration getInstance(String uri) throws ConfigurationException, URISyntaxException {
194 		return Configuration.getInstance(new URI(uri), (Collection)null);
195 	}
196 
197 	/**
198 	 * Returns an instance of a <code>Configuration</code> subclass
199 	 * accordingly to the provided URI monitoring it for updates which will be
200 	 * notified to the provided listener.
201 	 * 
202 	 * Monitoring interval can be set through the system property
203 	 * <code>smartconfig.monitor.delay</code> expressed in milliseconds and
204 	 * defaulting to <b>60000</b>.
205 	 * 
206 	 * @param uri the URI to access the configuration.
207 	 * @param listener a <i>Listener</i> interface implementation.
208 	 * @return an instance of a <code>Configuration</code> subclass.
209 	 * @throws ConfigurationException if no handler matches the specified URI or
210 	 *         an error is encountered trying to access the configuration URI.
211 	 */
212 	public final static Configuration getInstance(String uri, Listener listener) throws ConfigurationException,
213 			URISyntaxException {
214 		return Configuration.getInstance(new URI(uri), Collections.singletonList(listener));
215 	}
216 
217 	/**
218 	 * Returns an instance of a <code>Configuration</code> subclass
219 	 * accordingly to the provided URI monitoring it for updates which will be
220 	 * notified to the provided collection of listeners.
221 	 * 
222 	 * Monitoring interval can be set through the system property
223 	 * <code>smartconfig.monitor.delay</code> expressed in milliseconds and
224 	 * defaulting to <b>60000</b>.
225 	 * 
226 	 * @param uri the URI to access the configuration.
227 	 * @param listeners a collection of <i>Listener</i> interface implementors.
228 	 * @return an instance of a <code>Configuration</code> subclass.
229 	 * @throws ConfigurationException if no handler matches the specified URI or
230 	 *         an error is encountered trying to access the configuration URI.
231 	 */
232 	public final static Configuration getInstance(String uri, Collection listeners) throws ConfigurationException,
233 			URISyntaxException {
234 		return Configuration.getInstance(new URI(uri), listeners);
235 	}
236 
237 	/**
238 	 * Returns an instance of a <code>Configuration</code> subclass
239 	 * accordingly to the provided URI.
240 	 * 
241 	 * Monitoring interval can be set through the system property
242 	 * <code>smartconfig.monitor.delay</code> expressed in milliseconds and
243 	 * defaulting to <b>60000</b>.
244 	 * 
245 	 * @param uri the URI to access the configuration.
246 	 * @return an instance of a <code>Configuration</code> subclass.
247 	 * @throws ConfigurationException if no handler matches the specified URI or
248 	 *         an error is encountered trying to access the configuration URI.
249 	 */
250 	public final static Configuration getInstance(URI uri) throws ConfigurationException {
251 		return Configuration.getInstance(uri, (Collection)null);
252 	}
253 
254 	/**
255 	 * Returns an instance of a <code>Configuration</code> subclass
256 	 * accordingly to the provided URI monitoring it for updates which will be
257 	 * notified to the provided listener.
258 	 * 
259 	 * Monitoring interval can be set through the system property
260 	 * <code>smartconfig.monitor.delay</code> expressed in milliseconds and
261 	 * defaulting to <b>60000</b>.
262 	 * 
263 	 * @param uri the URI to access the configuration.
264 	 * @param listener a <i>Listener</i> interface implementation.
265 	 * @return an instance of a <code>Configuration</code> subclass.
266 	 * @throws ConfigurationException if no handler matches the specified URI or
267 	 *         an error is encountered trying to access the configuration URI.
268 	 */
269 	public final static Configuration getInstance(URI uri, Listener listener) throws ConfigurationException {
270 		return Configuration.getInstance(uri, Collections.singletonList(listener));
271 	}
272 
273 	/**
274 	 * Returns an instance of a <code>Configuration</code> subclass
275 	 * accordingly to the provided URI monitoring it for updates which will be
276 	 * notified to the provided collection of listeners.
277 	 * 
278 	 * Monitoring interval can be set through the system property
279 	 * <code>smartconfig.monitor.delay</code> expressed in milliseconds and
280 	 * defaulting to <b>60000</b>.
281 	 * 
282 	 * @param uri the URI to access the configuration.
283 	 * @param listeners a collection of <i>Listener</i> interface implementors.
284 	 * @return an instance of a <code>Configuration</code> subclass.
285 	 * @throws ConfigurationException if no handler matches the specified URI or
286 	 *         an error is encountered trying to access the configuration URI.
287 	 */
288 	public final static Configuration getInstance(URI uri, Collection listeners) throws ConfigurationException {
289 		Configuration result = null;
290 		if (uri.getScheme().equals("jdbc")) {
291 			try {
292 				StringTokenizer tokenizer = new StringTokenizer(uri.toString(), "&=");
293 				while (tokenizer.hasMoreTokens()) {
294 					String token = tokenizer.nextToken();
295 					if (token.equalsIgnoreCase("driver")) {
296 						Class.forName(tokenizer.nextToken());
297 					}
298 				}
299 				result = new SQLConfiguration(DriverManager.getConnection(uri.toString()));
300 			} catch (SQLException sqle) {
301 				throw new ConfigurationException(sqle);
302 			} catch (ClassNotFoundException cnfe) {
303 				throw new ConfigurationException(cnfe);
304 			}
305 		} else if (uri.getScheme().equals("file") || uri.getScheme().startsWith("http")
306 				|| uri.getScheme().equals("ftp")) {
307 			try {
308 				result = new XMLConfiguration(uri.toURL());
309 			} catch (java.net.MalformedURLException murle) {
310 				throw new ConfigurationException(murle);
311 			}
312 		} else {
313 			throw new ConfigurationException("No available handler for " + uri.getScheme());
314 		}
315 		if (listeners != null) {
316 			result.listeners.addAll(listeners);
317 		}
318 		return result;
319 	}
320 
321 	/**
322 	 * Checks if the underling configuration has been changed and notifies
323 	 * listeners of the change.
324 	 */
325 	protected final void check() {
326 		try {
327 			if (this.isChanged()) {
328 				this.update();
329 				Iterator notifications = this.listeners.iterator();
330 				while (notifications.hasNext()) {
331 					((Listener)notifications.next()).onUpdate(this);
332 				}
333 			}
334 		} catch (ConfigurationException ce) {
335 			// FIXME should we do something different like notify the listeners?
336 			ce.printStackTrace();
337 		}
338 	}
339 
340 
341 	/**
342 	 * Monitors the configuration for updates notifying changes to the
343 	 * registered listeners.
344 	 * 
345 	 * @author rlogiacco
346 	 */
347 	private class Monitor extends Thread {
348 
349 		/**
350 		 * Delay, in milliseconds, between configuration update checks.
351 		 */
352 		private long sleep = Long.parseLong(System.getProperty("smartconfig.monitor.delay", "60000"));
353 
354 
355 		/**
356 		 * @see java.lang.Thread#run()
357 		 */
358 		public void run() {
359 			while (true) {
360 				try {
361 					Thread.sleep(sleep);
362 				} catch (InterruptedException ie) {
363 					// Nothing to do
364 				}
365 				if (!Configuration.this.listeners.isEmpty()) {
366 					Configuration.this.check();
367 				}
368 			}
369 		}
370 	}
371 }