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 018package org.apache.commons.configuration2; 019 020import java.sql.Clob; 021import java.sql.Connection; 022import java.sql.PreparedStatement; 023import java.sql.ResultSet; 024import java.sql.SQLException; 025import java.sql.Statement; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.Iterator; 029import java.util.List; 030 031import javax.sql.DataSource; 032 033import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler; 034import org.apache.commons.configuration2.convert.ListDelimiterHandler; 035import org.apache.commons.configuration2.event.ConfigurationErrorEvent; 036import org.apache.commons.configuration2.event.ConfigurationEvent; 037import org.apache.commons.configuration2.event.EventType; 038import org.apache.commons.configuration2.io.ConfigurationLogger; 039import org.apache.commons.lang3.StringUtils; 040 041/** 042 * Configuration stored in a database. The properties are retrieved from a table containing at least one column for the 043 * keys, and one column for the values. It's possible to store several configurations in the same table by adding a 044 * column containing the name of the configuration. The name of the table and the columns have to be specified using the 045 * corresponding properties. 046 * <p> 047 * The recommended way to create an instance of {@code DatabaseConfiguration} is to use a <em>configuration 048 * builder</em>. The builder is configured with a special parameters object defining the database structures used by the 049 * configuration. Such an object can be created using the {@code database()} method of the {@code Parameters} class. See 050 * the examples below for more details. 051 * </p> 052 * 053 * <p> 054 * <strong>Example 1 - One configuration per table</strong> 055 * </p> 056 * 057 * <pre> 058 * CREATE TABLE myconfig ( 059 * `key` VARCHAR NOT NULL PRIMARY KEY, 060 * `value` VARCHAR 061 * ); 062 * 063 * INSERT INTO myconfig (key, value) VALUES ('foo', 'bar'); 064 * 065 * BasicConfigurationBuilder<DatabaseConfiguration> builder = 066 * new BasicConfigurationBuilder<DatabaseConfiguration>(DatabaseConfiguration.class); 067 * builder.configure( 068 * Parameters.database() 069 * .setDataSource(dataSource) 070 * .setTable("myconfig") 071 * .setKeyColumn("key") 072 * .setValueColumn("value") 073 * ); 074 * Configuration config = builder.getConfiguration(); 075 * String value = config.getString("foo"); 076 * </pre> 077 * 078 * <p> 079 * <strong>Example 2 - Multiple configurations per table</strong> 080 * </p> 081 * 082 * <pre> 083 * CREATE TABLE myconfigs ( 084 * `name` VARCHAR NOT NULL, 085 * `key` VARCHAR NOT NULL, 086 * `value` VARCHAR, 087 * CONSTRAINT sys_pk_myconfigs PRIMARY KEY (`name`, `key`) 088 * ); 089 * 090 * INSERT INTO myconfigs (name, key, value) VALUES ('config1', 'key1', 'value1'); 091 * INSERT INTO myconfigs (name, key, value) VALUES ('config2', 'key2', 'value2'); 092 * 093 * BasicConfigurationBuilder<DatabaseConfiguration> builder = 094 * new BasicConfigurationBuilder<DatabaseConfiguration>(DatabaseConfiguration.class); 095 * builder.configure( 096 * Parameters.database() 097 * .setDataSource(dataSource) 098 * .setTable("myconfigs") 099 * .setKeyColumn("key") 100 * .setValueColumn("value") 101 * .setConfigurationNameColumn("name") 102 * .setConfigurationName("config1") 103 * ); 104 * Configuration config1 = new DatabaseConfiguration(dataSource, "myconfigs", "name", "key", "value", "config1"); 105 * String value1 = conf.getString("key1"); 106 * </pre> 107 * 108 * The configuration can be instructed to perform commits after database updates. This is achieved by setting the 109 * {@code commits} parameter of the constructors to <b>true</b>. If commits should not be performed (which is the 110 * default behavior), it should be ensured that the connections returned by the {@code DataSource} are in auto-commit 111 * mode. 112 * <p> 113 * <strong>Note: Like JDBC itself, protection against SQL injection is left to the user.</strong> 114 * </p> 115 * 116 * @since 1.0 117 */ 118public class DatabaseConfiguration extends AbstractConfiguration { 119 /** 120 * An internally used helper class for simplifying database access through plain JDBC. This class provides a simple 121 * framework for creating and executing a JDBC statement. It especially takes care of proper handling of JDBC resources 122 * even in case of an error. 123 * 124 * @param <T> the type of the results produced by a JDBC operation 125 */ 126 private abstract class AbstractJdbcOperation<T> { 127 /** Stores the connection. */ 128 private Connection connection; 129 130 /** Stores the statement. */ 131 private PreparedStatement preparedStatement; 132 133 /** Stores the result set. */ 134 private ResultSet resultSet; 135 136 /** The type of the event to send in case of an error. */ 137 private final EventType<? extends ConfigurationErrorEvent> errorEventType; 138 139 /** The type of the operation which caused an error. */ 140 private final EventType<?> operationEventType; 141 142 /** The property configurationName for an error event. */ 143 private final String errorPropertyName; 144 145 /** The property value for an error event. */ 146 private final Object errorPropertyValue; 147 148 /** 149 * Creates a new instance of {@code JdbcOperation} and initializes the properties related to the error event. 150 * 151 * @param errEvType the type of the error event 152 * @param opType the operation event type 153 * @param errPropName the property configurationName for the error event 154 * @param errPropVal the property value for the error event 155 */ 156 protected AbstractJdbcOperation(final EventType<? extends ConfigurationErrorEvent> errEvType, final EventType<?> opType, final String errPropName, 157 final Object errPropVal) { 158 errorEventType = errEvType; 159 operationEventType = opType; 160 errorPropertyName = errPropName; 161 errorPropertyValue = errPropVal; 162 } 163 164 /** 165 * Creates a {@code PreparedStatement} object for executing the specified SQL statement. 166 * 167 * @param sql the statement to be executed 168 * @param nameCol a flag whether the configurationName column should be taken into account 169 * @return the prepared statement object 170 * @throws SQLException if an SQL error occurs 171 */ 172 protected PreparedStatement createStatement(final String sql, final boolean nameCol) throws SQLException { 173 final String statement; 174 if (nameCol && configurationNameColumn != null) { 175 final StringBuilder buf = new StringBuilder(sql); 176 buf.append(" AND ").append(configurationNameColumn).append("=?"); 177 statement = buf.toString(); 178 } else { 179 statement = sql; 180 } 181 182 preparedStatement = getConnection().prepareStatement(statement); 183 return preparedStatement; 184 } 185 186 /** 187 * Executes this operation. This method obtains a database connection and then delegates to {@code performOperation()}. 188 * Afterwards it performs the necessary clean up. Exceptions that are thrown during the JDBC operation are caught and 189 * transformed into configuration error events. 190 * 191 * @return the result of the operation 192 */ 193 public T execute() { 194 T result = null; 195 196 if (getDataSource() != null) { 197 try { 198 connection = getDataSource().getConnection(); 199 result = performOperation(); 200 201 if (isAutoCommit()) { 202 connection.commit(); 203 } 204 } catch (final SQLException e) { 205 fireError(errorEventType, operationEventType, errorPropertyName, errorPropertyValue, e); 206 } finally { 207 close(connection, preparedStatement, resultSet); 208 } 209 } 210 211 return result; 212 } 213 214 /** 215 * Gets the current connection. This method can be called while {@code execute()} is running. It returns <b>null</b> 216 * otherwise. 217 * 218 * @return the current connection 219 */ 220 protected Connection getConnection() { 221 return connection; 222 } 223 224 /** 225 * Creates an initializes a {@code PreparedStatement} object for executing an SQL statement. This method first calls 226 * {@code createStatement()} for creating the statement and then initializes the statement's parameters. 227 * 228 * @param sql the statement to be executed 229 * @param nameCol a flag whether the configurationName column should be taken into account 230 * @param params the parameters for the statement 231 * @return the initialized statement object 232 * @throws SQLException if an SQL error occurs 233 */ 234 protected PreparedStatement initStatement(final String sql, final boolean nameCol, final Object... params) throws SQLException { 235 final PreparedStatement ps = createStatement(sql, nameCol); 236 237 int idx = 1; 238 for (final Object param : params) { 239 ps.setObject(idx++, param); 240 } 241 if (nameCol && configurationNameColumn != null) { 242 ps.setString(idx, configurationName); 243 } 244 245 return ps; 246 } 247 248 /** 249 * Creates a {@code PreparedStatement} for a query, initializes it and executes it. The resulting {@code ResultSet} is 250 * returned. 251 * 252 * @param sql the statement to be executed 253 * @param nameCol a flag whether the configurationName column should be taken into account 254 * @param params the parameters for the statement 255 * @return the {@code ResultSet} produced by the query 256 * @throws SQLException if an SQL error occurs 257 */ 258 protected ResultSet openResultSet(final String sql, final boolean nameCol, final Object... params) throws SQLException { 259 return resultSet = initStatement(sql, nameCol, params).executeQuery(); 260 } 261 262 /** 263 * Performs the JDBC operation. This method is called by {@code execute()} after this object has been fully initialized. 264 * Here the actual JDBC logic has to be placed. 265 * 266 * @return the result of the operation 267 * @throws SQLException if an SQL error occurs 268 */ 269 protected abstract T performOperation() throws SQLException; 270 } 271 272 /** Constant for the statement used by getProperty. */ 273 private static final String SQL_GET_PROPERTY = "SELECT * FROM %s WHERE %s =?"; 274 275 /** Constant for the statement used by isEmpty. */ 276 private static final String SQL_IS_EMPTY = "SELECT count(*) FROM %s WHERE 1 = 1"; 277 278 /** Constant for the statement used by clearProperty. */ 279 private static final String SQL_CLEAR_PROPERTY = "DELETE FROM %s WHERE %s =?"; 280 281 /** Constant for the statement used by clear. */ 282 private static final String SQL_CLEAR = "DELETE FROM %s WHERE 1 = 1"; 283 284 /** Constant for the statement used by getKeys. */ 285 private static final String SQL_GET_KEYS = "SELECT DISTINCT %s FROM %s WHERE 1 = 1"; 286 287 /** 288 * Converts a CLOB to a string. 289 * 290 * @param clob the CLOB to be converted 291 * @return the extracted string value 292 * @throws SQLException if an error occurs 293 */ 294 private static Object convertClob(final Clob clob) throws SQLException { 295 final int len = (int) clob.length(); 296 return len > 0 ? clob.getSubString(1, len) : StringUtils.EMPTY; 297 } 298 299 /** The data source to connect to the database. */ 300 private DataSource dataSource; 301 302 /** The configurationName of the table containing the configurations. */ 303 private String table; 304 305 /** The column containing the configurationName of the configuration. */ 306 private String configurationNameColumn; 307 308 /** The column containing the keys. */ 309 private String keyColumn; 310 311 /** The column containing the values. */ 312 private String valueColumn; 313 314 /** The configurationName of the configuration. */ 315 private String configurationName; 316 317 /** A flag whether commits should be performed by this configuration. */ 318 private boolean autoCommit; 319 320 /** 321 * Creates a new instance of {@code DatabaseConfiguration}. 322 */ 323 public DatabaseConfiguration() { 324 initLogger(new ConfigurationLogger(DatabaseConfiguration.class)); 325 addErrorLogListener(); 326 } 327 328 /** 329 * Adds a property to this configuration. If this causes a database error, an error event will be generated of type 330 * {@code ADD_PROPERTY} with the causing exception. The event's {@code propertyName} is set to the passed in property 331 * key, the {@code propertyValue} points to the passed in value. 332 * 333 * @param key the property key 334 * @param obj the value of the property to add 335 */ 336 @Override 337 protected void addPropertyDirect(final String key, final Object obj) { 338 new AbstractJdbcOperation<Void>(ConfigurationErrorEvent.WRITE, ConfigurationEvent.ADD_PROPERTY, key, obj) { 339 @Override 340 protected Void performOperation() throws SQLException { 341 final StringBuilder query = new StringBuilder("INSERT INTO "); 342 query.append(table).append(" ("); 343 query.append(keyColumn).append(", "); 344 query.append(valueColumn); 345 if (configurationNameColumn != null) { 346 query.append(", ").append(configurationNameColumn); 347 } 348 query.append(") VALUES (?, ?"); 349 if (configurationNameColumn != null) { 350 query.append(", ?"); 351 } 352 query.append(")"); 353 354 try (PreparedStatement pstmt = initStatement(query.toString(), false, key, String.valueOf(obj))) { 355 if (configurationNameColumn != null) { 356 pstmt.setString(3, configurationName); 357 } 358 359 pstmt.executeUpdate(); 360 return null; 361 } 362 } 363 }.execute(); 364 } 365 366 /** 367 * Adds a property to this configuration. This implementation temporarily disables list delimiter parsing, so that even 368 * if the value contains the list delimiter, only a single record is written into the managed table. The implementation 369 * of {@code getProperty()} takes care about delimiters. So list delimiters are fully supported by 370 * {@code DatabaseConfiguration}, but internally treated a bit differently. 371 * 372 * @param key the key of the new property 373 * @param value the value to be added 374 */ 375 @Override 376 protected void addPropertyInternal(final String key, final Object value) { 377 final ListDelimiterHandler oldHandler = getListDelimiterHandler(); 378 try { 379 // temporarily disable delimiter parsing 380 setListDelimiterHandler(DisabledListDelimiterHandler.INSTANCE); 381 super.addPropertyInternal(key, value); 382 } finally { 383 setListDelimiterHandler(oldHandler); 384 } 385 } 386 387 /** 388 * Removes all entries from this configuration. If this causes a database error, an error event will be generated of 389 * type {@code CLEAR} with the causing exception. Both the event's {@code propertyName} and the {@code propertyValue} 390 * will be undefined. 391 */ 392 @Override 393 protected void clearInternal() { 394 new AbstractJdbcOperation<Void>(ConfigurationErrorEvent.WRITE, ConfigurationEvent.CLEAR, null, null) { 395 @Override 396 protected Void performOperation() throws SQLException { 397 try (PreparedStatement statement = initStatement(String.format(SQL_CLEAR, table), true)) { 398 statement.executeUpdate(); 399 } 400 return null; 401 } 402 }.execute(); 403 } 404 405 /** 406 * Removes the specified value from this configuration. If this causes a database error, an error event will be 407 * generated of type {@code CLEAR_PROPERTY} with the causing exception. The event's {@code propertyName} will be set to 408 * the passed in key, the {@code propertyValue} will be undefined. 409 * 410 * @param key the key of the property to be removed 411 */ 412 @Override 413 protected void clearPropertyDirect(final String key) { 414 new AbstractJdbcOperation<Void>(ConfigurationErrorEvent.WRITE, ConfigurationEvent.CLEAR_PROPERTY, key, null) { 415 @Override 416 protected Void performOperation() throws SQLException { 417 try (PreparedStatement ps = initStatement(String.format(SQL_CLEAR_PROPERTY, table, keyColumn), true, key)) { 418 ps.executeUpdate(); 419 return null; 420 } 421 } 422 }.execute(); 423 } 424 425 /** 426 * Close the specified database objects. Avoid closing if null and hide any SQLExceptions that occur. 427 * 428 * @param conn The database connection to close 429 * @param stmt The statement to close 430 * @param rs the result set to close 431 */ 432 protected void close(final Connection conn, final Statement stmt, final ResultSet rs) { 433 try { 434 if (rs != null) { 435 rs.close(); 436 } 437 } catch (final SQLException e) { 438 getLogger().error("An error occurred on closing the result set", e); 439 } 440 441 try { 442 if (stmt != null) { 443 stmt.close(); 444 } 445 } catch (final SQLException e) { 446 getLogger().error("An error occurred on closing the statement", e); 447 } 448 449 try { 450 if (conn != null) { 451 conn.close(); 452 } 453 } catch (final SQLException e) { 454 getLogger().error("An error occurred on closing the connection", e); 455 } 456 } 457 458 /** 459 * Checks whether this configuration contains the specified key. If this causes a database error, an error event will be 460 * generated of type {@code READ} with the causing exception. The event's {@code propertyName} will be set to the passed 461 * in key, the {@code propertyValue} will be undefined. 462 * 463 * @param key the key to be checked 464 * @return a flag whether this key is defined 465 */ 466 @Override 467 protected boolean containsKeyInternal(final String key) { 468 final AbstractJdbcOperation<Boolean> op = new AbstractJdbcOperation<Boolean>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, key, null) { 469 @Override 470 protected Boolean performOperation() throws SQLException { 471 try (ResultSet rs = openResultSet(String.format(SQL_GET_PROPERTY, table, keyColumn), true, key)) { 472 return rs.next(); 473 } 474 } 475 }; 476 477 final Boolean result = op.execute(); 478 return result != null && result.booleanValue(); 479 } 480 481 /** 482 * Tests whether this configuration contains one or more matches to this value. This operation stops at first 483 * match but may be more expensive than the containsKey method. 484 * @since 2.11.0 485 */ 486 @Override 487 protected boolean containsValueInternal(final Object value) { 488 final AbstractJdbcOperation<Boolean> op = new AbstractJdbcOperation<Boolean>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, null, value) { 489 @Override 490 protected Boolean performOperation() throws SQLException { 491 try (ResultSet rs = openResultSet(String.format(SQL_GET_PROPERTY, table, valueColumn), false, value)) { 492 return rs.next(); 493 } 494 } 495 }; 496 final Boolean result = op.execute(); 497 return result != null && result.booleanValue(); 498 } 499 500 /** 501 * Extracts the value of a property from the given result set. The passed in {@code ResultSet} was created by a SELECT 502 * statement on the underlying database table. This implementation reads the value of the column determined by the 503 * {@code valueColumn} property. Normally the contained value is directly returned. However, if it is of type 504 * {@code CLOB}, text is extracted as string. 505 * 506 * @param rs the current {@code ResultSet} 507 * @return the value of the property column 508 * @throws SQLException if an error occurs 509 */ 510 protected Object extractPropertyValue(final ResultSet rs) throws SQLException { 511 Object value = rs.getObject(valueColumn); 512 if (value instanceof Clob) { 513 value = convertClob((Clob) value); 514 } 515 return value; 516 } 517 518 /** 519 * Gets the name of this configuration instance. 520 * 521 * @return the name of this configuration 522 */ 523 public String getConfigurationName() { 524 return configurationName; 525 } 526 527 /** 528 * Gets the name of the table column with the configuration name. 529 * 530 * @return the name of the configuration name column 531 */ 532 public String getConfigurationNameColumn() { 533 return configurationNameColumn; 534 } 535 536 /** 537 * Gets the used {@code DataSource} object. 538 * 539 * @return the data source 540 * @since 1.4 541 * @deprecated Use {@link #getDataSource()} 542 */ 543 @Deprecated 544 public DataSource getDatasource() { 545 return dataSource; 546 } 547 548 /** 549 * Gets the {@code DataSource} for obtaining database connections. 550 * 551 * @return the {@code DataSource} 552 */ 553 public DataSource getDataSource() { 554 return dataSource; 555 } 556 557 /** 558 * Gets the name of the column containing the configuration keys. 559 * 560 * @return the name of the key column 561 */ 562 public String getKeyColumn() { 563 return keyColumn; 564 } 565 566 /** 567 * Returns an iterator with the names of all properties contained in this configuration. If this causes a database 568 * error, an error event will be generated of type {@code READ} with the causing exception. Both the event's 569 * {@code propertyName} and the {@code propertyValue} will be undefined. 570 * 571 * @return an iterator with the contained keys (an empty iterator in case of an error) 572 */ 573 @Override 574 protected Iterator<String> getKeysInternal() { 575 final Collection<String> keys = new ArrayList<>(); 576 new AbstractJdbcOperation<Collection<String>>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, null, null) { 577 @Override 578 protected Collection<String> performOperation() throws SQLException { 579 try (ResultSet rs = openResultSet(String.format(SQL_GET_KEYS, keyColumn, table), true)) { 580 while (rs.next()) { 581 keys.add(rs.getString(1)); 582 } 583 return keys; 584 } 585 } 586 }.execute(); 587 588 return keys.iterator(); 589 } 590 591 /** 592 * Gets the value of the specified property. If this causes a database error, an error event will be generated of 593 * type {@code READ} with the causing exception. The event's {@code propertyName} is set to the passed in property key, 594 * the {@code propertyValue} is undefined. 595 * 596 * @param key the key of the desired property 597 * @return the value of this property 598 */ 599 @Override 600 protected Object getPropertyInternal(final String key) { 601 final AbstractJdbcOperation<Object> op = new AbstractJdbcOperation<Object>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, key, null) { 602 @Override 603 protected Object performOperation() throws SQLException { 604 final List<Object> results = new ArrayList<>(); 605 try (ResultSet rs = openResultSet(String.format(SQL_GET_PROPERTY, table, keyColumn), true, key)) { 606 while (rs.next()) { 607 // Split value if it contains the list delimiter 608 getListDelimiterHandler().parse(extractPropertyValue(rs)).forEach(results::add); 609 } 610 } 611 if (!results.isEmpty()) { 612 return results.size() > 1 ? results : results.get(0); 613 } 614 return null; 615 } 616 }; 617 618 return op.execute(); 619 } 620 621 /** 622 * Gets the name of the table containing configuration data. 623 * 624 * @return the name of the table to be queried 625 */ 626 public String getTable() { 627 return table; 628 } 629 630 /** 631 * Gets the name of the column containing the configuration values. 632 * 633 * @return the name of the value column 634 */ 635 public String getValueColumn() { 636 return valueColumn; 637 } 638 639 /** 640 * Returns a flag whether this configuration performs commits after database updates. 641 * 642 * @return a flag whether commits are performed 643 */ 644 public boolean isAutoCommit() { 645 return autoCommit; 646 } 647 648 /** 649 * Checks if this configuration is empty. If this causes a database error, an error event will be generated of type 650 * {@code READ} with the causing exception. Both the event's {@code propertyName} and {@code propertyValue} will be 651 * undefined. 652 * 653 * @return a flag whether this configuration is empty. 654 */ 655 @Override 656 protected boolean isEmptyInternal() { 657 final AbstractJdbcOperation<Integer> op = new AbstractJdbcOperation<Integer>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, null, null) { 658 @Override 659 protected Integer performOperation() throws SQLException { 660 try (ResultSet rs = openResultSet(String.format(SQL_IS_EMPTY, table), true)) { 661 return rs.next() ? Integer.valueOf(rs.getInt(1)) : null; 662 } 663 } 664 }; 665 666 final Integer count = op.execute(); 667 return count == null || count.intValue() == 0; 668 } 669 670 /** 671 * Sets the auto commit flag. If set to <b>true</b>, this configuration performs a commit after each database update. 672 * 673 * @param autoCommit the auto commit flag 674 */ 675 public void setAutoCommit(final boolean autoCommit) { 676 this.autoCommit = autoCommit; 677 } 678 679 /** 680 * Sets the name of this configuration instance. 681 * 682 * @param configurationName the name of this configuration 683 */ 684 public void setConfigurationName(final String configurationName) { 685 this.configurationName = configurationName; 686 } 687 688 /** 689 * Sets the name of the table column with the configuration name. 690 * 691 * @param configurationNameColumn the name of the column with the configuration name 692 */ 693 public void setConfigurationNameColumn(final String configurationNameColumn) { 694 this.configurationNameColumn = configurationNameColumn; 695 } 696 697 /** 698 * Sets the {@code DataSource} for obtaining database connections. 699 * 700 * @param dataSource the {@code DataSource} 701 */ 702 public void setDataSource(final DataSource dataSource) { 703 this.dataSource = dataSource; 704 } 705 706 /** 707 * Sets the name of the column containing the configuration keys. 708 * 709 * @param keyColumn the name of the key column 710 */ 711 public void setKeyColumn(final String keyColumn) { 712 this.keyColumn = keyColumn; 713 } 714 715 /** 716 * Sets the name of the table containing configuration data. 717 * 718 * @param table the table name 719 */ 720 public void setTable(final String table) { 721 this.table = table; 722 } 723 724 /** 725 * Sets the name of the column containing the configuration values. 726 * 727 * @param valueColumn the name of the value column 728 */ 729 public void setValueColumn(final String valueColumn) { 730 this.valueColumn = valueColumn; 731 } 732}