Coverage Report - org.apache.commons.configuration.DatabaseConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
DatabaseConfiguration
94%
150/159
83%
39/47
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  
 }