001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.configuration;
019:
020: import java.sql.Connection;
021: import java.sql.PreparedStatement;
022: import java.sql.ResultSet;
023: import java.sql.SQLException;
024: import java.sql.Statement;
025: import java.util.ArrayList;
026: import java.util.Collection;
027: import java.util.Iterator;
028: import java.util.List;
029:
030: import javax.sql.DataSource;
031:
032: import org.apache.commons.collections.CollectionUtils;
033: import org.apache.commons.logging.LogFactory;
034:
035: /**
036: * Configuration stored in a database.
037: *
038: * @since 1.0
039: *
040: * @author Emmanuel Bourg
041: * @version $Revision: 514234 $, $Date: 2007-03-03 21:18:14 +0100 (Sa, 03 Mrz 2007) $
042: */
043: public class DatabaseConfiguration extends AbstractConfiguration {
044: /** The datasource to connect to the database. */
045: private DataSource datasource;
046:
047: /** The name of the table containing the configurations. */
048: private String table;
049:
050: /** The column containing the name of the configuration. */
051: private String nameColumn;
052:
053: /** The column containing the keys. */
054: private String keyColumn;
055:
056: /** The column containing the values. */
057: private String valueColumn;
058:
059: /** The name of the configuration. */
060: private String name;
061:
062: /**
063: * Build a configuration from a table containing multiple configurations.
064: *
065: * @param datasource the datasource to connect to the database
066: * @param table the name of the table containing the configurations
067: * @param nameColumn the column containing the name of the configuration
068: * @param keyColumn the column containing the keys of the configuration
069: * @param valueColumn the column containing the values of the configuration
070: * @param name the name of the configuration
071: */
072: public DatabaseConfiguration(DataSource datasource, String table,
073: String nameColumn, String keyColumn, String valueColumn,
074: String name) {
075: this .datasource = datasource;
076: this .table = table;
077: this .nameColumn = nameColumn;
078: this .keyColumn = keyColumn;
079: this .valueColumn = valueColumn;
080: this .name = name;
081: setLogger(LogFactory.getLog(getClass()));
082: addErrorLogListener(); // log errors per default
083: }
084:
085: /**
086: * Build a configuration from a table.-
087: *
088: * @param datasource the datasource to connect to the database
089: * @param table the name of the table containing the configurations
090: * @param keyColumn the column containing the keys of the configuration
091: * @param valueColumn the column containing the values of the configuration
092: */
093: public DatabaseConfiguration(DataSource datasource, String table,
094: String keyColumn, String valueColumn) {
095: this (datasource, table, null, keyColumn, valueColumn, null);
096: }
097:
098: /**
099: * Returns the value of the specified property. If this causes a database
100: * error, an error event will be generated of type
101: * <code>EVENT_READ_PROPERTY</code> with the causing exception. The
102: * event's <code>propertyName</code> is set to the passed in property key,
103: * the <code>propertyValue</code> is undefined.
104: *
105: * @param key the key of the desired property
106: * @return the value of this property
107: */
108: public Object getProperty(String key) {
109: Object result = null;
110:
111: // build the query
112: StringBuffer query = new StringBuffer("SELECT * FROM ");
113: query.append(table).append(" WHERE ");
114: query.append(keyColumn).append("=?");
115: if (nameColumn != null) {
116: query.append(" AND " + nameColumn + "=?");
117: }
118:
119: Connection conn = null;
120: PreparedStatement pstmt = null;
121:
122: try {
123: conn = getConnection();
124:
125: // bind the parameters
126: pstmt = conn.prepareStatement(query.toString());
127: pstmt.setString(1, key);
128: if (nameColumn != null) {
129: pstmt.setString(2, name);
130: }
131:
132: ResultSet rs = pstmt.executeQuery();
133:
134: List results = new ArrayList();
135: while (rs.next()) {
136: Object val = rs.getObject(valueColumn);
137: if (isDelimiterParsingDisabled()) {
138: results.add(val);
139: } else {
140: // Split value if it containts the list delimiter
141: CollectionUtils.addAll(results, PropertyConverter
142: .toIterator(val, getListDelimiter()));
143: }
144: }
145:
146: if (!results.isEmpty()) {
147: result = (results.size() > 1) ? results : results
148: .get(0);
149: }
150: } catch (SQLException e) {
151: fireError(EVENT_READ_PROPERTY, key, null, e);
152: } finally {
153: closeQuietly(conn, pstmt);
154: }
155:
156: return result;
157: }
158:
159: /**
160: * Adds a property to this configuration. If this causes a database error,
161: * an error event will be generated of type <code>EVENT_ADD_PROPERTY</code>
162: * with the causing exception. The event's <code>propertyName</code> is
163: * set to the passed in property key, the <code>propertyValue</code>
164: * points to the passed in value.
165: *
166: * @param key the property key
167: * @param obj the value of the property to add
168: */
169: protected void addPropertyDirect(String key, Object obj) {
170: // build the query
171: StringBuffer query = new StringBuffer("INSERT INTO " + table);
172: if (nameColumn != null) {
173: query.append(" (" + nameColumn + ", " + keyColumn + ", "
174: + valueColumn + ") VALUES (?, ?, ?)");
175: } else {
176: query.append(" (" + keyColumn + ", " + valueColumn
177: + ") VALUES (?, ?)");
178: }
179:
180: Connection conn = null;
181: PreparedStatement pstmt = null;
182:
183: try {
184: conn = getConnection();
185:
186: // bind the parameters
187: pstmt = conn.prepareStatement(query.toString());
188: int index = 1;
189: if (nameColumn != null) {
190: pstmt.setString(index++, name);
191: }
192: pstmt.setString(index++, key);
193: pstmt.setString(index++, String.valueOf(obj));
194:
195: pstmt.executeUpdate();
196: } catch (SQLException e) {
197: fireError(EVENT_ADD_PROPERTY, key, obj, e);
198: } finally {
199: // clean up
200: closeQuietly(conn, pstmt);
201: }
202: }
203:
204: /**
205: * Adds a property to this configuration. This implementation will
206: * temporarily disable list delimiter parsing, so that even if the value
207: * contains the list delimiter, only a single record will be written into
208: * the managed table. The implementation of <code>getProperty()</code>
209: * will take care about delimiters. So list delimiters are fully supported
210: * by <code>DatabaseConfiguration</code>, but internally treated a bit
211: * differently.
212: *
213: * @param key the key of the new property
214: * @param value the value to be added
215: */
216: public void addProperty(String key, Object value) {
217: boolean parsingFlag = isDelimiterParsingDisabled();
218: try {
219: if (value instanceof String) {
220: // temporarily disable delimiter parsing
221: setDelimiterParsingDisabled(true);
222: }
223: super .addProperty(key, value);
224: } finally {
225: setDelimiterParsingDisabled(parsingFlag);
226: }
227: }
228:
229: /**
230: * Checks if this configuration is empty. If this causes a database error,
231: * an error event will be generated of type <code>EVENT_READ_PROPERTY</code>
232: * with the causing exception. Both the event's <code>propertyName</code>
233: * and <code>propertyValue</code> will be undefined.
234: *
235: * @return a flag whether this configuration is empty.
236: */
237: public boolean isEmpty() {
238: boolean empty = true;
239:
240: // build the query
241: StringBuffer query = new StringBuffer("SELECT count(*) FROM "
242: + table);
243: if (nameColumn != null) {
244: query.append(" WHERE " + nameColumn + "=?");
245: }
246:
247: Connection conn = null;
248: PreparedStatement pstmt = null;
249:
250: try {
251: conn = getConnection();
252:
253: // bind the parameters
254: pstmt = conn.prepareStatement(query.toString());
255: if (nameColumn != null) {
256: pstmt.setString(1, name);
257: }
258:
259: ResultSet rs = pstmt.executeQuery();
260:
261: if (rs.next()) {
262: empty = rs.getInt(1) == 0;
263: }
264: } catch (SQLException e) {
265: fireError(EVENT_READ_PROPERTY, null, null, e);
266: } finally {
267: // clean up
268: closeQuietly(conn, pstmt);
269: }
270:
271: return empty;
272: }
273:
274: /**
275: * Checks whether this configuration contains the specified key. If this
276: * causes a database error, an error event will be generated of type
277: * <code>EVENT_READ_PROPERTY</code> with the causing exception. The
278: * event's <code>propertyName</code> will be set to the passed in key, the
279: * <code>propertyValue</code> will be undefined.
280: *
281: * @param key the key to be checked
282: * @return a flag whether this key is defined
283: */
284: public boolean containsKey(String key) {
285: boolean found = false;
286:
287: // build the query
288: StringBuffer query = new StringBuffer("SELECT * FROM " + table
289: + " WHERE " + keyColumn + "=?");
290: if (nameColumn != null) {
291: query.append(" AND " + nameColumn + "=?");
292: }
293:
294: Connection conn = null;
295: PreparedStatement pstmt = null;
296:
297: try {
298: conn = getConnection();
299:
300: // bind the parameters
301: pstmt = conn.prepareStatement(query.toString());
302: pstmt.setString(1, key);
303: if (nameColumn != null) {
304: pstmt.setString(2, name);
305: }
306:
307: ResultSet rs = pstmt.executeQuery();
308:
309: found = rs.next();
310: } catch (SQLException e) {
311: fireError(EVENT_READ_PROPERTY, key, null, e);
312: } finally {
313: // clean up
314: closeQuietly(conn, pstmt);
315: }
316:
317: return found;
318: }
319:
320: /**
321: * Removes the specified value from this configuration. If this causes a
322: * database error, an error event will be generated of type
323: * <code>EVENT_CLEAR_PROPERTY</code> with the causing exception. The
324: * event's <code>propertyName</code> will be set to the passed in key, the
325: * <code>propertyValue</code> will be undefined.
326: *
327: * @param key the key of the property to be removed
328: */
329: public void clearProperty(String key) {
330: // build the query
331: StringBuffer query = new StringBuffer("DELETE FROM " + table
332: + " WHERE " + keyColumn + "=?");
333: if (nameColumn != null) {
334: query.append(" AND " + nameColumn + "=?");
335: }
336:
337: Connection conn = null;
338: PreparedStatement pstmt = null;
339:
340: try {
341: conn = getConnection();
342:
343: // bind the parameters
344: pstmt = conn.prepareStatement(query.toString());
345: pstmt.setString(1, key);
346: if (nameColumn != null) {
347: pstmt.setString(2, name);
348: }
349:
350: pstmt.executeUpdate();
351: } catch (SQLException e) {
352: fireError(EVENT_CLEAR_PROPERTY, key, null, e);
353: } finally {
354: // clean up
355: closeQuietly(conn, pstmt);
356: }
357: }
358:
359: /**
360: * Removes all entries from this configuration. If this causes a database
361: * error, an error event will be generated of type
362: * <code>EVENT_CLEAR</code> with the causing exception. Both the
363: * event's <code>propertyName</code> and the <code>propertyValue</code>
364: * will be undefined.
365: */
366: public void clear() {
367: // build the query
368: StringBuffer query = new StringBuffer("DELETE FROM " + table);
369: if (nameColumn != null) {
370: query.append(" WHERE " + nameColumn + "=?");
371: }
372:
373: Connection conn = null;
374: PreparedStatement pstmt = null;
375:
376: try {
377: conn = getConnection();
378:
379: // bind the parameters
380: pstmt = conn.prepareStatement(query.toString());
381: if (nameColumn != null) {
382: pstmt.setString(1, name);
383: }
384:
385: pstmt.executeUpdate();
386: } catch (SQLException e) {
387: fireError(EVENT_CLEAR, null, null, e);
388: } finally {
389: // clean up
390: closeQuietly(conn, pstmt);
391: }
392: }
393:
394: /**
395: * Returns an iterator with the names of all properties contained in this
396: * configuration. If this causes a database
397: * error, an error event will be generated of type
398: * <code>EVENT_READ_PROPERTY</code> with the causing exception. Both the
399: * event's <code>propertyName</code> and the <code>propertyValue</code>
400: * will be undefined.
401: * @return an iterator with the contained keys (an empty iterator in case
402: * of an error)
403: */
404: public Iterator getKeys() {
405: Collection keys = new ArrayList();
406:
407: // build the query
408: StringBuffer query = new StringBuffer("SELECT DISTINCT "
409: + keyColumn + " FROM " + table);
410: if (nameColumn != null) {
411: query.append(" WHERE " + nameColumn + "=?");
412: }
413:
414: Connection conn = null;
415: PreparedStatement pstmt = null;
416:
417: try {
418: conn = getConnection();
419:
420: // bind the parameters
421: pstmt = conn.prepareStatement(query.toString());
422: if (nameColumn != null) {
423: pstmt.setString(1, name);
424: }
425:
426: ResultSet rs = pstmt.executeQuery();
427:
428: while (rs.next()) {
429: keys.add(rs.getString(1));
430: }
431: } catch (SQLException e) {
432: fireError(EVENT_READ_PROPERTY, null, null, e);
433: } finally {
434: // clean up
435: closeQuietly(conn, pstmt);
436: }
437:
438: return keys.iterator();
439: }
440:
441: /**
442: * Returns the used <code>DataSource</code> object.
443: *
444: * @return the data source
445: * @since 1.4
446: */
447: public DataSource getDatasource() {
448: return datasource;
449: }
450:
451: /**
452: * Returns a <code>Connection</code> object. This method is called when
453: * ever the database is to be accessed. This implementation returns a
454: * connection from the current <code>DataSource</code>.
455: *
456: * @return the <code>Connection</code> object to be used
457: * @throws SQLException if an error occurs
458: * @since 1.4
459: */
460: protected Connection getConnection() throws SQLException {
461: return getDatasource().getConnection();
462: }
463:
464: /**
465: * Close a <code>Connection</code> and, <code>Statement</code>.
466: * Avoid closing if null and hide any SQLExceptions that occur.
467: *
468: * @param conn The database connection to close
469: * @param stmt The statement to close
470: */
471: private void closeQuietly(Connection conn, Statement stmt) {
472: try {
473: if (stmt != null) {
474: stmt.close();
475: }
476: if (conn != null) {
477: conn.close();
478: }
479: } catch (SQLException e) {
480: getLogger().error(e.getMessage(), e);
481: }
482: }
483: }
|