Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DatabaseConfiguration |
|
| 3.3076923076923075;3,308 |
1 | /* | |
2 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
3 | * contributor license agreements. See the NOTICE file distributed with | |
4 | * this work for additional information regarding copyright ownership. | |
5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
6 | * (the "License"); you may not use this file except in compliance with | |
7 | * the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
16 | */ | |
17 | ||
18 | package org.apache.commons.configuration; | |
19 | ||
20 | import java.sql.Connection; | |
21 | import java.sql.PreparedStatement; | |
22 | import java.sql.ResultSet; | |
23 | import java.sql.SQLException; | |
24 | import java.sql.Statement; | |
25 | import java.util.ArrayList; | |
26 | import java.util.Collection; | |
27 | import java.util.Iterator; | |
28 | import java.util.List; | |
29 | ||
30 | import javax.sql.DataSource; | |
31 | ||
32 | import org.apache.commons.collections.CollectionUtils; | |
33 | import org.apache.commons.logging.LogFactory; | |
34 | ||
35 | /** | |
36 | * Configuration stored in a database. | |
37 | * | |
38 | * @since 1.0 | |
39 | * | |
40 | * @author Emmanuel Bourg | |
41 | * @version $Revision: 514234 $, $Date: 2007-03-03 21:18:14 +0100 (Sa, 03 Mrz 2007) $ | |
42 | */ | |
43 | public class DatabaseConfiguration extends AbstractConfiguration | |
44 | { | |
45 | /** The datasource to connect to the database. */ | |
46 | private DataSource datasource; | |
47 | ||
48 | /** The name of the table containing the configurations. */ | |
49 | private String table; | |
50 | ||
51 | /** The column containing the name of the configuration. */ | |
52 | private String nameColumn; | |
53 | ||
54 | /** The column containing the keys. */ | |
55 | private String keyColumn; | |
56 | ||
57 | /** The column containing the values. */ | |
58 | private String valueColumn; | |
59 | ||
60 | /** The name of the configuration. */ | |
61 | private String name; | |
62 | ||
63 | /** | |
64 | * Build a configuration from a table containing multiple configurations. | |
65 | * | |
66 | * @param datasource the datasource to connect to the database | |
67 | * @param table the name of the table containing the configurations | |
68 | * @param nameColumn the column containing the name of the configuration | |
69 | * @param keyColumn the column containing the keys of the configuration | |
70 | * @param valueColumn the column containing the values of the configuration | |
71 | * @param name the name of the configuration | |
72 | */ | |
73 | public DatabaseConfiguration(DataSource datasource, String table, String nameColumn, | |
74 | String keyColumn, String valueColumn, String name) | |
75 | 30 | { |
76 | 30 | this.datasource = datasource; |
77 | 30 | this.table = table; |
78 | 30 | this.nameColumn = nameColumn; |
79 | 30 | this.keyColumn = keyColumn; |
80 | 30 | this.valueColumn = valueColumn; |
81 | 30 | this.name = name; |
82 | 30 | setLogger(LogFactory.getLog(getClass())); |
83 | 30 | addErrorLogListener(); // log errors per default |
84 | 30 | } |
85 | ||
86 | /** | |
87 | * Build a configuration from a table.- | |
88 | * | |
89 | * @param datasource the datasource to connect to the database | |
90 | * @param table the name of the table containing the configurations | |
91 | * @param keyColumn the column containing the keys of the configuration | |
92 | * @param valueColumn the column containing the values of the configuration | |
93 | */ | |
94 | public DatabaseConfiguration(DataSource datasource, String table, String keyColumn, String valueColumn) | |
95 | { | |
96 | 22 | this(datasource, table, null, keyColumn, valueColumn, null); |
97 | 22 | } |
98 | ||
99 | /** | |
100 | * Returns the value of the specified property. If this causes a database | |
101 | * error, an error event will be generated of type | |
102 | * <code>EVENT_READ_PROPERTY</code> with the causing exception. The | |
103 | * event's <code>propertyName</code> is set to the passed in property key, | |
104 | * the <code>propertyValue</code> is undefined. | |
105 | * | |
106 | * @param key the key of the desired property | |
107 | * @return the value of this property | |
108 | */ | |
109 | public Object getProperty(String key) | |
110 | { | |
111 | 11 | Object result = null; |
112 | ||
113 | // build the query | |
114 | 11 | StringBuffer query = new StringBuffer("SELECT * FROM "); |
115 | 11 | query.append(table).append(" WHERE "); |
116 | 11 | query.append(keyColumn).append("=?"); |
117 | 11 | if (nameColumn != null) |
118 | { | |
119 | 3 | query.append(" AND " + nameColumn + "=?"); |
120 | } | |
121 | ||
122 | 11 | Connection conn = null; |
123 | 11 | PreparedStatement pstmt = null; |
124 | ||
125 | try | |
126 | { | |
127 | 11 | conn = getConnection(); |
128 | ||
129 | // bind the parameters | |
130 | 10 | pstmt = conn.prepareStatement(query.toString()); |
131 | 10 | pstmt.setString(1, key); |
132 | 10 | if (nameColumn != null) |
133 | { | |
134 | 3 | pstmt.setString(2, name); |
135 | } | |
136 | ||
137 | 10 | ResultSet rs = pstmt.executeQuery(); |
138 | ||
139 | 10 | List results = new ArrayList(); |
140 | 30 | while (rs.next()) |
141 | { | |
142 | 10 | Object val = rs.getObject(valueColumn); |
143 | 10 | if (isDelimiterParsingDisabled()) |
144 | { | |
145 | 1 | results.add(val); |
146 | } | |
147 | else | |
148 | { | |
149 | // Split value if it containts the list delimiter | |
150 | 9 | CollectionUtils.addAll(results, PropertyConverter |
151 | .toIterator(val, getListDelimiter())); | |
152 | } | |
153 | } | |
154 | ||
155 | 10 | if (!results.isEmpty()) |
156 | { | |
157 | 8 | result = (results.size() > 1) ? results : results.get(0); |
158 | } | |
159 | 10 | } |
160 | catch (SQLException e) | |
161 | { | |
162 | 1 | fireError(EVENT_READ_PROPERTY, key, null, e); |
163 | 1 | } |
164 | finally | |
165 | { | |
166 | 0 | closeQuietly(conn, pstmt); |
167 | } | |
168 | ||
169 | 11 | return result; |
170 | } | |
171 | ||
172 | /** | |
173 | * Adds a property to this configuration. If this causes a database error, | |
174 | * an error event will be generated of type <code>EVENT_ADD_PROPERTY</code> | |
175 | * with the causing exception. The event's <code>propertyName</code> is | |
176 | * set to the passed in property key, the <code>propertyValue</code> | |
177 | * points to the passed in value. | |
178 | * | |
179 | * @param key the property key | |
180 | * @param obj the value of the property to add | |
181 | */ | |
182 | protected void addPropertyDirect(String key, Object obj) | |
183 | { | |
184 | // build the query | |
185 | 5 | StringBuffer query = new StringBuffer("INSERT INTO " + table); |
186 | 5 | if (nameColumn != null) |
187 | { | |
188 | 1 | query.append(" (" + nameColumn + ", " + keyColumn + ", " + valueColumn + ") VALUES (?, ?, ?)"); |
189 | } | |
190 | else | |
191 | { | |
192 | 4 | query.append(" (" + keyColumn + ", " + valueColumn + ") VALUES (?, ?)"); |
193 | } | |
194 | ||
195 | 5 | Connection conn = null; |
196 | 5 | PreparedStatement pstmt = null; |
197 | ||
198 | try | |
199 | { | |
200 | 5 | conn = getConnection(); |
201 | ||
202 | // bind the parameters | |
203 | 4 | pstmt = conn.prepareStatement(query.toString()); |
204 | 4 | int index = 1; |
205 | 4 | if (nameColumn != null) |
206 | { | |
207 | 1 | pstmt.setString(index++, name); |
208 | } | |
209 | 4 | pstmt.setString(index++, key); |
210 | 4 | pstmt.setString(index++, String.valueOf(obj)); |
211 | ||
212 | 4 | pstmt.executeUpdate(); |
213 | 4 | } |
214 | catch (SQLException e) | |
215 | { | |
216 | 1 | fireError(EVENT_ADD_PROPERTY, key, obj, e); |
217 | 1 | } |
218 | finally | |
219 | { | |
220 | // clean up | |
221 | 0 | closeQuietly(conn, pstmt); |
222 | } | |
223 | 5 | } |
224 | ||
225 | /** | |
226 | * Adds a property to this configuration. This implementation will | |
227 | * temporarily disable list delimiter parsing, so that even if the value | |
228 | * contains the list delimiter, only a single record will be written into | |
229 | * the managed table. The implementation of <code>getProperty()</code> | |
230 | * will take care about delimiters. So list delimiters are fully supported | |
231 | * by <code>DatabaseConfiguration</code>, but internally treated a bit | |
232 | * differently. | |
233 | * | |
234 | * @param key the key of the new property | |
235 | * @param value the value to be added | |
236 | */ | |
237 | public void addProperty(String key, Object value) | |
238 | { | |
239 | 2 | boolean parsingFlag = isDelimiterParsingDisabled(); |
240 | try | |
241 | { | |
242 | 2 | if (value instanceof String) |
243 | { | |
244 | // temporarily disable delimiter parsing | |
245 | 2 | setDelimiterParsingDisabled(true); |
246 | } | |
247 | 2 | super.addProperty(key, value); |
248 | 2 | } |
249 | finally | |
250 | { | |
251 | 0 | setDelimiterParsingDisabled(parsingFlag); |
252 | } | |
253 | 2 | } |
254 | ||
255 | /** | |
256 | * Checks if this configuration is empty. If this causes a database error, | |
257 | * an error event will be generated of type <code>EVENT_READ_PROPERTY</code> | |
258 | * with the causing exception. Both the event's <code>propertyName</code> | |
259 | * and <code>propertyValue</code> will be undefined. | |
260 | * | |
261 | * @return a flag whether this configuration is empty. | |
262 | */ | |
263 | public boolean isEmpty() | |
264 | { | |
265 | 7 | boolean empty = true; |
266 | ||
267 | // build the query | |
268 | 7 | StringBuffer query = new StringBuffer("SELECT count(*) FROM " + table); |
269 | 7 | if (nameColumn != null) |
270 | { | |
271 | 3 | query.append(" WHERE " + nameColumn + "=?"); |
272 | } | |
273 | ||
274 | 7 | Connection conn = null; |
275 | 7 | PreparedStatement pstmt = null; |
276 | ||
277 | try | |
278 | { | |
279 | 7 | conn = getConnection(); |
280 | ||
281 | // bind the parameters | |
282 | 6 | pstmt = conn.prepareStatement(query.toString()); |
283 | 6 | if (nameColumn != null) |
284 | { | |
285 | 3 | pstmt.setString(1, name); |
286 | } | |
287 | ||
288 | 6 | ResultSet rs = pstmt.executeQuery(); |
289 | ||
290 | 6 | if (rs.next()) |
291 | { | |
292 | 6 | empty = rs.getInt(1) == 0; |
293 | } | |
294 | 6 | } |
295 | catch (SQLException e) | |
296 | { | |
297 | 1 | fireError(EVENT_READ_PROPERTY, null, null, e); |
298 | 1 | } |
299 | finally | |
300 | { | |
301 | // clean up | |
302 | 0 | closeQuietly(conn, pstmt); |
303 | } | |
304 | ||
305 | 7 | return empty; |
306 | } | |
307 | ||
308 | /** | |
309 | * Checks whether this configuration contains the specified key. If this | |
310 | * causes a database error, an error event will be generated of type | |
311 | * <code>EVENT_READ_PROPERTY</code> with the causing exception. The | |
312 | * event's <code>propertyName</code> will be set to the passed in key, the | |
313 | * <code>propertyValue</code> will be undefined. | |
314 | * | |
315 | * @param key the key to be checked | |
316 | * @return a flag whether this key is defined | |
317 | */ | |
318 | public boolean containsKey(String key) | |
319 | { | |
320 | 11 | boolean found = false; |
321 | ||
322 | // build the query | |
323 | 11 | StringBuffer query = new StringBuffer("SELECT * FROM " + table + " WHERE " + keyColumn + "=?"); |
324 | 11 | if (nameColumn != null) |
325 | { | |
326 | 4 | query.append(" AND " + nameColumn + "=?"); |
327 | } | |
328 | ||
329 | 11 | Connection conn = null; |
330 | 11 | PreparedStatement pstmt = null; |
331 | ||
332 | try | |
333 | { | |
334 | 11 | conn = getConnection(); |
335 | ||
336 | // bind the parameters | |
337 | 10 | pstmt = conn.prepareStatement(query.toString()); |
338 | 10 | pstmt.setString(1, key); |
339 | 10 | if (nameColumn != null) |
340 | { | |
341 | 4 | pstmt.setString(2, name); |
342 | } | |
343 | ||
344 | 10 | ResultSet rs = pstmt.executeQuery(); |
345 | ||
346 | 10 | found = rs.next(); |
347 | 10 | } |
348 | catch (SQLException e) | |
349 | { | |
350 | 1 | fireError(EVENT_READ_PROPERTY, key, null, e); |
351 | 1 | } |
352 | finally | |
353 | { | |
354 | // clean up | |
355 | 0 | closeQuietly(conn, pstmt); |
356 | } | |
357 | ||
358 | 11 | return found; |
359 | } | |
360 | ||
361 | /** | |
362 | * Removes the specified value from this configuration. If this causes a | |
363 | * database error, an error event will be generated of type | |
364 | * <code>EVENT_CLEAR_PROPERTY</code> with the causing exception. The | |
365 | * event's <code>propertyName</code> will be set to the passed in key, the | |
366 | * <code>propertyValue</code> will be undefined. | |
367 | * | |
368 | * @param key the key of the property to be removed | |
369 | */ | |
370 | public void clearProperty(String key) | |
371 | { | |
372 | // build the query | |
373 | 4 | StringBuffer query = new StringBuffer("DELETE FROM " + table + " WHERE " + keyColumn + "=?"); |
374 | 4 | if (nameColumn != null) |
375 | { | |
376 | 1 | query.append(" AND " + nameColumn + "=?"); |
377 | } | |
378 | ||
379 | 4 | Connection conn = null; |
380 | 4 | PreparedStatement pstmt = null; |
381 | ||
382 | try | |
383 | { | |
384 | 4 | conn = getConnection(); |
385 | ||
386 | // bind the parameters | |
387 | 3 | pstmt = conn.prepareStatement(query.toString()); |
388 | 3 | pstmt.setString(1, key); |
389 | 3 | if (nameColumn != null) |
390 | { | |
391 | 1 | pstmt.setString(2, name); |
392 | } | |
393 | ||
394 | 3 | pstmt.executeUpdate(); |
395 | 3 | } |
396 | catch (SQLException e) | |
397 | { | |
398 | 1 | fireError(EVENT_CLEAR_PROPERTY, key, null, e); |
399 | 1 | } |
400 | finally | |
401 | { | |
402 | // clean up | |
403 | 0 | closeQuietly(conn, pstmt); |
404 | } | |
405 | 4 | } |
406 | ||
407 | /** | |
408 | * Removes all entries from this configuration. If this causes a database | |
409 | * error, an error event will be generated of type | |
410 | * <code>EVENT_CLEAR</code> with the causing exception. Both the | |
411 | * event's <code>propertyName</code> and the <code>propertyValue</code> | |
412 | * will be undefined. | |
413 | */ | |
414 | public void clear() | |
415 | { | |
416 | // build the query | |
417 | 3 | StringBuffer query = new StringBuffer("DELETE FROM " + table); |
418 | 3 | if (nameColumn != null) |
419 | { | |
420 | 1 | query.append(" WHERE " + nameColumn + "=?"); |
421 | } | |
422 | ||
423 | 3 | Connection conn = null; |
424 | 3 | PreparedStatement pstmt = null; |
425 | ||
426 | try | |
427 | { | |
428 | 3 | conn = getConnection(); |
429 | ||
430 | // bind the parameters | |
431 | 2 | pstmt = conn.prepareStatement(query.toString()); |
432 | 2 | if (nameColumn != null) |
433 | { | |
434 | 1 | pstmt.setString(1, name); |
435 | } | |
436 | ||
437 | 2 | pstmt.executeUpdate(); |
438 | 2 | } |
439 | catch (SQLException e) | |
440 | { | |
441 | 1 | fireError(EVENT_CLEAR, null, null, e); |
442 | 1 | } |
443 | finally | |
444 | { | |
445 | // clean up | |
446 | 0 | closeQuietly(conn, pstmt); |
447 | } | |
448 | 3 | } |
449 | ||
450 | /** | |
451 | * Returns an iterator with the names of all properties contained in this | |
452 | * configuration. If this causes a database | |
453 | * error, an error event will be generated of type | |
454 | * <code>EVENT_READ_PROPERTY</code> with the causing exception. Both the | |
455 | * event's <code>propertyName</code> and the <code>propertyValue</code> | |
456 | * will be undefined. | |
457 | * @return an iterator with the contained keys (an empty iterator in case | |
458 | * of an error) | |
459 | */ | |
460 | public Iterator getKeys() | |
461 | { | |
462 | 6 | Collection keys = new ArrayList(); |
463 | ||
464 | // build the query | |
465 | 6 | StringBuffer query = new StringBuffer("SELECT DISTINCT " + keyColumn + " FROM " + table); |
466 | 6 | if (nameColumn != null) |
467 | { | |
468 | 1 | query.append(" WHERE " + nameColumn + "=?"); |
469 | } | |
470 | ||
471 | 6 | Connection conn = null; |
472 | 6 | PreparedStatement pstmt = null; |
473 | ||
474 | try | |
475 | { | |
476 | 6 | conn = getConnection(); |
477 | ||
478 | // bind the parameters | |
479 | 5 | pstmt = conn.prepareStatement(query.toString()); |
480 | 5 | if (nameColumn != null) |
481 | { | |
482 | 1 | pstmt.setString(1, name); |
483 | } | |
484 | ||
485 | 5 | ResultSet rs = pstmt.executeQuery(); |
486 | ||
487 | 21 | while (rs.next()) |
488 | { | |
489 | 11 | keys.add(rs.getString(1)); |
490 | } | |
491 | 5 | } |
492 | catch (SQLException e) | |
493 | { | |
494 | 1 | fireError(EVENT_READ_PROPERTY, null, null, e); |
495 | 1 | } |
496 | finally | |
497 | { | |
498 | // clean up | |
499 | 0 | closeQuietly(conn, pstmt); |
500 | } | |
501 | ||
502 | 6 | return keys.iterator(); |
503 | } | |
504 | ||
505 | /** | |
506 | * Returns the used <code>DataSource</code> object. | |
507 | * | |
508 | * @return the data source | |
509 | * @since 1.4 | |
510 | */ | |
511 | public DataSource getDatasource() | |
512 | { | |
513 | 40 | return datasource; |
514 | } | |
515 | ||
516 | /** | |
517 | * Returns a <code>Connection</code> object. This method is called when | |
518 | * ever the database is to be accessed. This implementation returns a | |
519 | * connection from the current <code>DataSource</code>. | |
520 | * | |
521 | * @return the <code>Connection</code> object to be used | |
522 | * @throws SQLException if an error occurs | |
523 | * @since 1.4 | |
524 | */ | |
525 | protected Connection getConnection() throws SQLException | |
526 | { | |
527 | 40 | return getDatasource().getConnection(); |
528 | } | |
529 | ||
530 | /** | |
531 | * Close a <code>Connection</code> and, <code>Statement</code>. | |
532 | * Avoid closing if null and hide any SQLExceptions that occur. | |
533 | * | |
534 | * @param conn The database connection to close | |
535 | * @param stmt The statement to close | |
536 | */ | |
537 | private void closeQuietly(Connection conn, Statement stmt) | |
538 | { | |
539 | try | |
540 | { | |
541 | 47 | if (stmt != null) |
542 | { | |
543 | 40 | stmt.close(); |
544 | } | |
545 | 47 | if (conn != null) |
546 | { | |
547 | 40 | conn.close(); |
548 | } | |
549 | 47 | } |
550 | catch (SQLException e) | |
551 | { | |
552 | 0 | getLogger().error(e.getMessage(), e); |
553 | } | |
554 | 47 | } |
555 | } |