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 }