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.io.PrintWriter;
26  import java.sql.CallableStatement;
27  import java.sql.Connection;
28  import java.sql.DatabaseMetaData;
29  import java.sql.PreparedStatement;
30  import java.sql.ResultSet;
31  import java.sql.SQLException;
32  import java.sql.SQLWarning;
33  import java.sql.Savepoint;
34  import java.sql.Statement;
35  import java.util.Map;
36  
37  import javax.sql.DataSource;
38  
39  /**
40   * Represents a configuration expressed through a database table.
41   * 
42   * @author rlogiacco
43   */
44  public final class SQLConfiguration extends Configuration {
45  
46  	/**
47  	 * The database connection pool.
48  	 */
49  	private DataSource pool;
50  
51  	/**
52  	 * Last access time to the underlying byte representation. Used to identify
53  	 * a possible need to update the in-memory representation.
54  	 */
55  	private long updated;
56  
57  	/**
58  	 * Name used to identify the last update time on the database.
59  	 */
60  	protected final static String LAST_UPDATE = "'smartconfig.last_update'";
61  
62  
63  	/**
64  	 * Constructs a configuration structure from a JDBC connection.
65  	 * 
66  	 * The providen connection must be active and open and should not be closed
67  	 * from outside otherwise any subsequent update check will fail.
68  	 * 
69  	 * @param connection the connection to use to retrieve informations from the
70  	 *        database.
71  	 * @throws ConfigurationException
72  	 */
73  	public SQLConfiguration(Connection connection) throws ConfigurationException {
74  		this(new SimpleDataSource(connection));
75  	}
76  
77  	/**
78  	 * Constructs a configuration structure from a JDBC data source.
79  	 * 
80  	 * @param pool the data source providing database connections.
81  	 * @throws ConfigurationException
82  	 */
83  	public SQLConfiguration(DataSource pool) throws ConfigurationException {
84  		this.pool = pool;
85  		this.update();
86  	}
87  
88  	/**
89  	 * @see net.smartlab.config.Configuration#isChanged()
90  	 */
91  	public boolean isChanged() throws ConfigurationException {
92  		return this.getLastUpdate(false) > this.updated;
93  	}
94  
95  	/**
96  	 * @see net.smartlab.config.Configuration#update()
97  	 */
98  	public void update() throws ConfigurationException {
99  		Connection connection = null;
100 		try {
101 			connection = pool.getConnection();
102 			PreparedStatement statement = connection.prepareStatement("SELECT name, value FROM config WHERE name <> "
103 					+ LAST_UPDATE);
104 			ResultSet rows = statement.executeQuery();
105 			while (rows.next()) {
106 				String name = rows.getString(1);
107 				String value = rows.getString(2);
108 				this.add(name, value);
109 			}
110 			this.updated = this.getLastUpdate(true);
111 		} catch (SQLException sqle) {
112 			throw new ConfigurationException(sqle);
113 		} finally {
114 			try {
115 				connection.close();
116 			} catch (SQLException sqle) {
117 				sqle.printStackTrace();
118 			} catch (NullPointerException npe) {
119 				System.out.println("Unable to retrieve connection");
120 			}
121 		}
122 	}
123 
124 	/**
125 	 * Extracts the last modification timestamp from the underling database.
126 	 * 
127 	 * @param set specifies if the last modification time must be set if not
128 	 *        found
129 	 * @return the last modification timestamp.
130 	 * @throws ConfigurationException if something unexpected happens trying to
131 	 *         retrieve the last modification timestamp or if the corresponding
132 	 *         parameter has not been defined.
133 	 */
134 	public long getLastUpdate(boolean set) throws ConfigurationException {
135 		Connection connection = null;
136 		try {
137 			connection = pool.getConnection();
138 			PreparedStatement statement = connection.prepareStatement("SELECT value FROM config WHERE name = "
139 					+ LAST_UPDATE);
140 			ResultSet rows = statement.executeQuery();
141 			if (rows.next()) {
142 				return Long.parseLong(rows.getString(1));
143 			} else if (set) {
144 				long time = System.currentTimeMillis();
145 				this.setLastUpdate(time);
146 				return time;
147 			} else {
148 				throw new ConfigurationException("Cannot find special configuration parameter " + LAST_UPDATE);
149 			}
150 		} catch (SQLException sqle) {
151 			throw new ConfigurationException(sqle);
152 		} finally {
153 			try {
154 				connection.close();
155 			} catch (SQLException sqle) {
156 				sqle.printStackTrace();
157 			} catch (NullPointerException npe) {
158 				System.out.println("Unable to retrieve connection");
159 			}
160 		}
161 	}
162 
163 	/**
164 	 * Sets the last modification timestamp into the underling database.
165 	 * 
166 	 * @param timestamp the last modification timestamp to set.
167 	 * @throws ConfigurationException f something unexpected happens trying to
168 	 *         set the last modification timestamp.
169 	 */
170 	public void setLastUpdate(long timestamp) throws ConfigurationException {
171 		Connection connection = null;
172 		try {
173 			connection = pool.getConnection();
174 			try {
175 				connection.prepareStatement("UPDATE config SET value='" + timestamp + "' WHERE name=" + LAST_UPDATE)
176 						.execute();
177 			} catch (SQLException sqle) {
178 				connection.prepareStatement("INSERT INTO config VALUES (" + LAST_UPDATE + ",'" + timestamp + "')")
179 						.execute();
180 			}
181 		} catch (SQLException sqle) {
182 			throw new ConfigurationException(sqle);
183 		} finally {
184 			try {
185 				connection.close();
186 			} catch (SQLException sqle) {
187 				sqle.printStackTrace();
188 			} catch (NullPointerException npe) {
189 				System.out.println("Unable to retrieve connection");
190 			}
191 		}
192 	}
193 
194 	/**
195 	 * Adds informations to the in-memory configuration.
196 	 * 
197 	 * @param fqn the fully qualified name as stored into the table
198 	 * @param value the value relative to the fqn providen
199 	 * @throws ConfigurationException if somethign wrong occurs while adding the
200 	 *         informations to the configuration
201 	 */
202 	private Element add(String fqn, String value) throws ConfigurationException {
203 		String path = null;
204 		String name = fqn;
205 		int index = -1;
206 		String attribute = null;
207 		int pathLength = fqn.lastIndexOf(".");
208 		int itemSeparator = fqn.indexOf("#");
209 		int attributeStart = fqn.indexOf("[");
210 		if (pathLength > -1) {
211 			path = fqn.substring(0, pathLength);
212 			name = fqn.substring(pathLength + 1);
213 		}
214 		if (itemSeparator > -1) {
215 			if (attributeStart > -1) {
216 				name = fqn.substring(pathLength + 1, itemSeparator);
217 				index = Integer.parseInt(fqn.substring(itemSeparator + 1, attributeStart));
218 				attribute = fqn.substring(attributeStart + 1, fqn.length() - 1);
219 			} else {
220 				name = fqn.substring(pathLength + 1, itemSeparator);
221 				index = Integer.parseInt(fqn.substring(itemSeparator + 1));
222 			}
223 		} else if (attributeStart > -1) {
224 			name = fqn.substring(pathLength + 1, attributeStart);
225 			attribute = fqn.substring(attributeStart + 1, fqn.length() - 1);
226 		}
227 		Node parent = null;
228 		Element node = null;
229 		if (path != null) {
230 			parent = this.findNode(this, path);
231 			if (parent == null) {
232 				parent = (Node)this.add(path, null);
233 			}
234 			if (index > -1) {
235 				try {
236 					node = (Element)parent.getElements(name).toArray()[index - 1];
237 				} catch (Exception e) {
238 					node = null;
239 				}
240 			} else {
241 				node = (Element)parent.getElement(name);
242 			}
243 		} else if (this.name == null || this.name.equals(name)) {
244 			node = this;
245 			node.name = name;
246 		} else {
247 			throw new ConfigurationException("Configuration tree can have only one root node: found " + name
248 					+ " conflicting with " + this.name);
249 		}
250 		if (node == null) {
251 			if (attribute != null && attribute.equals(Reference.REFERENCE)) {
252 				node = new Reference(parent, name, value);
253 			} else {
254 				node = new Node(parent, name, null);
255 			}
256 			if (index > -1) {
257 				try {
258 					parent.children.set(index - 1, node);
259 				} catch (IndexOutOfBoundsException ioobe) {
260 					parent.children.add(index - 1, node);
261 				}
262 			} else {
263 				parent.children.add(node);
264 			}
265 		}
266 		if (!(node instanceof Reference)) {
267 			if (attribute == null) {
268 				if (value != null && value.trim().length() > 0) {
269 					((Node)node).setContent(value);
270 				}
271 			} else {
272 				((Node)node).attributes.put(attribute, value);
273 			}
274 		}
275 		return node;
276 	}
277 
278 	/**
279 	 * Recursively search a node up in the configuration tree
280 	 * 
281 	 * @param node
282 	 * @param path
283 	 * @return
284 	 * @throws ConfigurationException
285 	 */
286 	private Node findNode(Node node, String path) throws ConfigurationException {
287 		if (node instanceof SQLConfiguration && path.startsWith(node.name + '.')) {
288 			path = path.substring(path.indexOf('.') + 1);
289 		}
290 		if (node == null || path.equals(node.name)) {
291 			return node;
292 		}
293 		int index = path.indexOf('.');
294 		if (index > -1) {
295 			String child = path.substring(0, index);
296 			return this.findNode((Node)node.getElement(child), path.substring(index + 1));
297 		} else {
298 			return (Node)node.getElement(path);
299 		}
300 	}
301 
302 
303 	/**
304 	 * A basic datasource implementation to wrap a single database connection.
305 	 * 
306 	 * @author rlogiacco
307 	 * @deprecated
308 	 */
309 	private static class SimpleDataSource implements DataSource {
310 
311 		/**
312 		 * @uml.property name="connection"
313 		 */
314 		private Connection connection;
315 
316 
317 		public SimpleDataSource(Connection connection) {
318 			this.connection = new ConnectionWrapper(connection);
319 		}
320 
321 		/**
322 		 * TODO documentation
323 		 * 
324 		 * @return
325 		 * @throws SQLException
326 		 * @uml.property name="connection"
327 		 */
328 		public Connection getConnection() throws SQLException {
329 			return connection;
330 		}
331 
332 		public Connection getConnection(String username, String password) throws SQLException {
333 			throw new UnsupportedOperationException();
334 		}
335 
336 		public PrintWriter getLogWriter() throws SQLException {
337 			throw new UnsupportedOperationException();
338 		}
339 
340 		public int getLoginTimeout() throws SQLException {
341 			throw new UnsupportedOperationException();
342 		}
343 
344 		public void setLogWriter(PrintWriter out) throws SQLException {
345 			throw new UnsupportedOperationException();
346 		}
347 
348 		public void setLoginTimeout(int seconds) throws SQLException {
349 			throw new UnsupportedOperationException();
350 		}
351 
352 
353 		/**
354 		 * Wraps a connection to avoid it being closed.
355 		 * 
356 		 * @author rlogiacco
357 		 */
358 		private static class ConnectionWrapper implements Connection {
359 
360 			private Connection connection;
361 
362 
363 			public ConnectionWrapper(Connection connection) {
364 				this.connection = connection;
365 			}
366 
367 			/**
368 			 * @see java.sql.Connection#clearWarnings()
369 			 */
370 			public void clearWarnings() throws SQLException {
371 				connection.clearWarnings();
372 			}
373 
374 			/**
375 			 * @see java.sql.Connection#close()
376 			 */
377 			public void close() throws SQLException {
378 				// Nothing
379 			}
380 
381 			/**
382 			 * @see java.sql.Connection#commit()
383 			 */
384 			public void commit() throws SQLException {
385 				connection.commit();
386 			}
387 
388 			/**
389 			 * @see java.sql.Connection#createStatement()
390 			 */
391 			public Statement createStatement() throws SQLException {
392 				return connection.createStatement();
393 			}
394 
395 			/**
396 			 * @see java.sql.Connection#createStatement(int, int)
397 			 */
398 			public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
399 				return connection.createStatement(resultSetType, resultSetConcurrency);
400 			}
401 
402 			/**
403 			 * @see java.sql.Connection#createStatement(int, int, int)
404 			 */
405 			public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
406 					throws SQLException {
407 				return connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
408 			}
409 
410 			/**
411 			 * @see java.sql.Connection#getAutoCommit()
412 			 */
413 			public boolean getAutoCommit() throws SQLException {
414 				return connection.getAutoCommit();
415 			}
416 
417 			/**
418 			 * @see java.sql.Connection#getCatalog()
419 			 */
420 			public String getCatalog() throws SQLException {
421 				return connection.getCatalog();
422 			}
423 
424 			/**
425 			 * @see java.sql.Connection#getHoldability()
426 			 */
427 			public int getHoldability() throws SQLException {
428 				return connection.getHoldability();
429 			}
430 
431 			/**
432 			 * @see java.sql.Connection#getMetaData()
433 			 */
434 			public DatabaseMetaData getMetaData() throws SQLException {
435 				return connection.getMetaData();
436 			}
437 
438 			/**
439 			 * @see java.sql.Connection#getTransactionIsolation()
440 			 */
441 			public int getTransactionIsolation() throws SQLException {
442 				return connection.getTransactionIsolation();
443 			}
444 
445 			/**
446 			 * @see java.sql.Connection#getTypeMap()
447 			 */
448 			public Map getTypeMap() throws SQLException {
449 				return connection.getTypeMap();
450 			}
451 
452 			/**
453 			 * @see java.sql.Connection#getWarnings()
454 			 */
455 			public SQLWarning getWarnings() throws SQLException {
456 				return connection.getWarnings();
457 			}
458 
459 			/**
460 			 * @see java.sql.Connection#isClosed()
461 			 */
462 			public boolean isClosed() throws SQLException {
463 				return connection.isClosed();
464 			}
465 
466 			/**
467 			 * @see java.sql.Connection#isReadOnly()
468 			 */
469 			public boolean isReadOnly() throws SQLException {
470 				return connection.isReadOnly();
471 			}
472 
473 			/**
474 			 * @see java.sql.Connection#nativeSQL(java.lang.String)
475 			 */
476 			public String nativeSQL(String sql) throws SQLException {
477 				return connection.nativeSQL(sql);
478 			}
479 
480 			/**
481 			 * @see java.sql.Connection#prepareCall(java.lang.String)
482 			 */
483 			public CallableStatement prepareCall(String sql) throws SQLException {
484 				return connection.prepareCall(sql);
485 			}
486 
487 			/**
488 			 * @see java.sql.Connection#prepareCall(java.lang.String, int, int)
489 			 */
490 			public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
491 					throws SQLException {
492 				return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
493 			}
494 
495 			/**
496 			 * @see java.sql.Connection#prepareCall(java.lang.String, int, int,
497 			 *      int)
498 			 */
499 			public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
500 					int resultSetHoldability) throws SQLException {
501 				return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
502 			}
503 
504 			/**
505 			 * @see java.sql.Connection#prepareStatement(java.lang.String)
506 			 */
507 			public PreparedStatement prepareStatement(String sql) throws SQLException {
508 				return connection.prepareStatement(sql);
509 			}
510 
511 			/**
512 			 * @see java.sql.Connection#prepareStatement(java.lang.String, int)
513 			 */
514 			public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
515 				return connection.prepareStatement(sql, autoGeneratedKeys);
516 			}
517 
518 			/**
519 			 * @see java.sql.Connection#prepareStatement(java.lang.String,
520 			 *      int[])
521 			 */
522 			public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
523 				return connection.prepareStatement(sql, columnIndexes);
524 			}
525 
526 			/**
527 			 * @see java.sql.Connection#prepareStatement(java.lang.String,
528 			 *      java.lang.String[])
529 			 */
530 			public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
531 				return connection.prepareStatement(sql, columnNames);
532 			}
533 
534 			/**
535 			 * @see java.sql.Connection#prepareStatement(java.lang.String, int,
536 			 *      int)
537 			 */
538 			public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
539 					throws SQLException {
540 				return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
541 			}
542 
543 			/**
544 			 * @see java.sql.Connection#prepareStatement(java.lang.String, int,
545 			 *      int, int)
546 			 */
547 			public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
548 					int resultSetHoldability) throws SQLException {
549 				return connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
550 			}
551 
552 			/**
553 			 * @see java.sql.Connection#releaseSavepoint(java.sql.Savepoint)
554 			 */
555 			public void releaseSavepoint(Savepoint savepoint) throws SQLException {
556 				connection.releaseSavepoint(savepoint);
557 			}
558 
559 			/**
560 			 * @see java.sql.Connection#rollback()
561 			 */
562 			public void rollback() throws SQLException {
563 				connection.rollback();
564 			}
565 
566 			/**
567 			 * @see java.sql.Connection#rollback(java.sql.Savepoint)
568 			 */
569 			public void rollback(Savepoint savepoint) throws SQLException {
570 				connection.rollback(savepoint);
571 			}
572 
573 			/**
574 			 * @see java.sql.Connection#setAutoCommit(boolean)
575 			 */
576 			public void setAutoCommit(boolean autoCommit) throws SQLException {
577 				connection.setAutoCommit(autoCommit);
578 			}
579 
580 			/**
581 			 * @see java.sql.Connection#setCatalog(java.lang.String)
582 			 */
583 			public void setCatalog(String catalog) throws SQLException {
584 				connection.setCatalog(catalog);
585 			}
586 
587 			/**
588 			 * @see java.sql.Connection#setHoldability(int)
589 			 */
590 			public void setHoldability(int holdability) throws SQLException {
591 				connection.setHoldability(holdability);
592 			}
593 
594 			/**
595 			 * @see java.sql.Connection#setReadOnly(boolean)
596 			 */
597 			public void setReadOnly(boolean readOnly) throws SQLException {
598 				connection.setReadOnly(readOnly);
599 			}
600 
601 			/**
602 			 * @see java.sql.Connection#setSavepoint()
603 			 */
604 			public Savepoint setSavepoint() throws SQLException {
605 				return connection.setSavepoint();
606 			}
607 
608 			/**
609 			 * @see java.sql.Connection#setSavepoint(java.lang.String)
610 			 */
611 			public Savepoint setSavepoint(String name) throws SQLException {
612 				return connection.setSavepoint(name);
613 			}
614 
615 			/**
616 			 * @see java.sql.Connection#setTransactionIsolation(int)
617 			 */
618 			public void setTransactionIsolation(int level) throws SQLException {
619 				connection.setTransactionIsolation(level);
620 			}
621 
622 			/**
623 			 * @see java.sql.Connection#setTypeMap(java.util.Map)
624 			 */
625 			public void setTypeMap(Map typeMap) throws SQLException {
626 				connection.setTypeMap(typeMap);
627 			}
628 		}
629 	}
630 }