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 }