DatabaseConfiguration.java

  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. package org.apache.commons.configuration2;

  18. import java.sql.Clob;
  19. import java.sql.Connection;
  20. import java.sql.PreparedStatement;
  21. import java.sql.ResultSet;
  22. import java.sql.SQLException;
  23. import java.sql.Statement;
  24. import java.util.ArrayList;
  25. import java.util.Collection;
  26. import java.util.Iterator;
  27. import java.util.List;

  28. import javax.sql.DataSource;

  29. import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
  30. import org.apache.commons.configuration2.convert.ListDelimiterHandler;
  31. import org.apache.commons.configuration2.event.ConfigurationErrorEvent;
  32. import org.apache.commons.configuration2.event.ConfigurationEvent;
  33. import org.apache.commons.configuration2.event.EventType;
  34. import org.apache.commons.configuration2.io.ConfigurationLogger;
  35. import org.apache.commons.lang3.StringUtils;

  36. /**
  37.  * Configuration stored in a database. The properties are retrieved from a table containing at least one column for the
  38.  * keys, and one column for the values. It's possible to store several configurations in the same table by adding a
  39.  * column containing the name of the configuration. The name of the table and the columns have to be specified using the
  40.  * corresponding properties.
  41.  * <p>
  42.  * The recommended way to create an instance of {@code DatabaseConfiguration} is to use a <em>configuration
  43.  * builder</em>. The builder is configured with a special parameters object defining the database structures used by the
  44.  * configuration. Such an object can be created using the {@code database()} method of the {@code Parameters} class. See
  45.  * the examples below for more details.
  46.  * </p>
  47.  *
  48.  * <p>
  49.  * <strong>Example 1 - One configuration per table</strong>
  50.  * </p>
  51.  *
  52.  * <pre>
  53.  * CREATE TABLE myconfig (
  54.  *     `key`   VARCHAR NOT NULL PRIMARY KEY,
  55.  *     `value` VARCHAR
  56.  * );
  57.  *
  58.  * INSERT INTO myconfig (key, value) VALUES ('foo', 'bar');
  59.  *
  60.  * BasicConfigurationBuilder&lt;DatabaseConfiguration&gt; builder =
  61.  *     new BasicConfigurationBuilder&lt;DatabaseConfiguration&gt;(DatabaseConfiguration.class);
  62.  * builder.configure(
  63.  *     Parameters.database()
  64.  *         .setDataSource(dataSource)
  65.  *         .setTable("myconfig")
  66.  *         .setKeyColumn("key")
  67.  *         .setValueColumn("value")
  68.  * );
  69.  * Configuration config = builder.getConfiguration();
  70.  * String value = config.getString("foo");
  71.  * </pre>
  72.  *
  73.  * <p>
  74.  * <strong>Example 2 - Multiple configurations per table</strong>
  75.  * </p>
  76.  *
  77.  * <pre>
  78.  * CREATE TABLE myconfigs (
  79.  *     `name`  VARCHAR NOT NULL,
  80.  *     `key`   VARCHAR NOT NULL,
  81.  *     `value` VARCHAR,
  82.  *     CONSTRAINT sys_pk_myconfigs PRIMARY KEY (`name`, `key`)
  83.  * );
  84.  *
  85.  * INSERT INTO myconfigs (name, key, value) VALUES ('config1', 'key1', 'value1');
  86.  * INSERT INTO myconfigs (name, key, value) VALUES ('config2', 'key2', 'value2');
  87.  *
  88.  * BasicConfigurationBuilder&lt;DatabaseConfiguration&gt; builder =
  89.  *     new BasicConfigurationBuilder&lt;DatabaseConfiguration&gt;(DatabaseConfiguration.class);
  90.  * builder.configure(
  91.  *     Parameters.database()
  92.  *         .setDataSource(dataSource)
  93.  *         .setTable("myconfigs")
  94.  *         .setKeyColumn("key")
  95.  *         .setValueColumn("value")
  96.  *         .setConfigurationNameColumn("name")
  97.  *         .setConfigurationName("config1")
  98.  * );
  99.  * Configuration config1 = new DatabaseConfiguration(dataSource, "myconfigs", "name", "key", "value", "config1");
  100.  * String value1 = conf.getString("key1");
  101.  * </pre>
  102.  *
  103.  * The configuration can be instructed to perform commits after database updates. This is achieved by setting the
  104.  * {@code commits} parameter of the constructors to <strong>true</strong>. If commits should not be performed (which is the
  105.  * default behavior), it should be ensured that the connections returned by the {@code DataSource} are in auto-commit
  106.  * mode.
  107.  * <p>
  108.  * <strong>Note: Like JDBC itself, protection against SQL injection is left to the user.</strong>
  109.  * </p>
  110.  *
  111.  * @since 1.0
  112.  */
  113. public class DatabaseConfiguration extends AbstractConfiguration {
  114.     /**
  115.      * An internally used helper class for simplifying database access through plain JDBC. This class provides a simple
  116.      * framework for creating and executing a JDBC statement. It especially takes care of proper handling of JDBC resources
  117.      * even in case of an error.
  118.      *
  119.      * @param <T> the type of the results produced by a JDBC operation
  120.      */
  121.     private abstract class AbstractJdbcOperation<T> {
  122.         /** Stores the connection. */
  123.         private Connection connection;

  124.         /** Stores the statement. */
  125.         private PreparedStatement preparedStatement;

  126.         /** Stores the result set. */
  127.         private ResultSet resultSet;

  128.         /** The type of the event to send in case of an error. */
  129.         private final EventType<? extends ConfigurationErrorEvent> errorEventType;

  130.         /** The type of the operation which caused an error. */
  131.         private final EventType<?> operationEventType;

  132.         /** The property configurationName for an error event. */
  133.         private final String errorPropertyName;

  134.         /** The property value for an error event. */
  135.         private final Object errorPropertyValue;

  136.         /**
  137.          * Creates a new instance of {@code JdbcOperation} and initializes the properties related to the error event.
  138.          *
  139.          * @param errEvType the type of the error event
  140.          * @param opType the operation event type
  141.          * @param errPropName the property configurationName for the error event
  142.          * @param errPropVal the property value for the error event
  143.          */
  144.         protected AbstractJdbcOperation(final EventType<? extends ConfigurationErrorEvent> errEvType, final EventType<?> opType, final String errPropName,
  145.             final Object errPropVal) {
  146.             errorEventType = errEvType;
  147.             operationEventType = opType;
  148.             errorPropertyName = errPropName;
  149.             errorPropertyValue = errPropVal;
  150.         }

  151.         /**
  152.          * Creates a {@code PreparedStatement} object for executing the specified SQL statement.
  153.          *
  154.          * @param sql the statement to be executed
  155.          * @param nameCol a flag whether the configurationName column should be taken into account
  156.          * @return the prepared statement object
  157.          * @throws SQLException if an SQL error occurs
  158.          */
  159.         protected PreparedStatement createStatement(final String sql, final boolean nameCol) throws SQLException {
  160.             final String statement;
  161.             if (nameCol && configurationNameColumn != null) {
  162.                 final StringBuilder buf = new StringBuilder(sql);
  163.                 buf.append(" AND ").append(configurationNameColumn).append("=?");
  164.                 statement = buf.toString();
  165.             } else {
  166.                 statement = sql;
  167.             }

  168.             preparedStatement = getConnection().prepareStatement(statement);
  169.             return preparedStatement;
  170.         }

  171.         /**
  172.          * Executes this operation. This method obtains a database connection and then delegates to {@code performOperation()}.
  173.          * Afterwards it performs the necessary clean up. Exceptions that are thrown during the JDBC operation are caught and
  174.          * transformed into configuration error events.
  175.          *
  176.          * @return the result of the operation
  177.          */
  178.         public T execute() {
  179.             T result = null;

  180.             if (getDataSource() != null) {
  181.                 try {
  182.                     connection = getDataSource().getConnection();
  183.                     result = performOperation();

  184.                     if (isAutoCommit()) {
  185.                         connection.commit();
  186.                     }
  187.                 } catch (final SQLException e) {
  188.                     fireError(errorEventType, operationEventType, errorPropertyName, errorPropertyValue, e);
  189.                 } finally {
  190.                     close(connection, preparedStatement, resultSet);
  191.                 }
  192.             }

  193.             return result;
  194.         }

  195.         /**
  196.          * Gets the current connection. This method can be called while {@code execute()} is running. It returns <strong>null</strong>
  197.          * otherwise.
  198.          *
  199.          * @return the current connection
  200.          */
  201.         protected Connection getConnection() {
  202.             return connection;
  203.         }

  204.         /**
  205.          * Creates an initializes a {@code PreparedStatement} object for executing an SQL statement. This method first calls
  206.          * {@code createStatement()} for creating the statement and then initializes the statement's parameters.
  207.          *
  208.          * @param sql the statement to be executed
  209.          * @param nameCol a flag whether the configurationName column should be taken into account
  210.          * @param params the parameters for the statement
  211.          * @return the initialized statement object
  212.          * @throws SQLException if an SQL error occurs
  213.          */
  214.         protected PreparedStatement initStatement(final String sql, final boolean nameCol, final Object... params) throws SQLException {
  215.             final PreparedStatement ps = createStatement(sql, nameCol);

  216.             int idx = 1;
  217.             for (final Object param : params) {
  218.                 ps.setObject(idx++, param);
  219.             }
  220.             if (nameCol && configurationNameColumn != null) {
  221.                 ps.setString(idx, configurationName);
  222.             }

  223.             return ps;
  224.         }

  225.         /**
  226.          * Creates a {@code PreparedStatement} for a query, initializes it and executes it. The resulting {@code ResultSet} is
  227.          * returned.
  228.          *
  229.          * @param sql the statement to be executed
  230.          * @param nameCol a flag whether the configurationName column should be taken into account
  231.          * @param params the parameters for the statement
  232.          * @return the {@code ResultSet} produced by the query
  233.          * @throws SQLException if an SQL error occurs
  234.          */
  235.         protected ResultSet openResultSet(final String sql, final boolean nameCol, final Object... params) throws SQLException {
  236.             return resultSet = initStatement(sql, nameCol, params).executeQuery();
  237.         }

  238.         /**
  239.          * Performs the JDBC operation. This method is called by {@code execute()} after this object has been fully initialized.
  240.          * Here the actual JDBC logic has to be placed.
  241.          *
  242.          * @return the result of the operation
  243.          * @throws SQLException if an SQL error occurs
  244.          */
  245.         protected abstract T performOperation() throws SQLException;
  246.     }

  247.     /** Constant for the statement used by getProperty. */
  248.     private static final String SQL_GET_PROPERTY = "SELECT * FROM %s WHERE %s =?";

  249.     /** Constant for the statement used by isEmpty. */
  250.     private static final String SQL_IS_EMPTY = "SELECT count(*) FROM %s WHERE 1 = 1";

  251.     /** Constant for the statement used by clearProperty. */
  252.     private static final String SQL_CLEAR_PROPERTY = "DELETE FROM %s WHERE %s =?";

  253.     /** Constant for the statement used by clear. */
  254.     private static final String SQL_CLEAR = "DELETE FROM %s WHERE 1 = 1";

  255.     /** Constant for the statement used by getKeys. */
  256.     private static final String SQL_GET_KEYS = "SELECT DISTINCT %s FROM %s WHERE 1 = 1";

  257.     /**
  258.      * Converts a CLOB to a string.
  259.      *
  260.      * @param clob the CLOB to be converted
  261.      * @return the extracted string value
  262.      * @throws SQLException if an error occurs
  263.      */
  264.     private static Object convertClob(final Clob clob) throws SQLException {
  265.         final int len = (int) clob.length();
  266.         return len > 0 ? clob.getSubString(1, len) : StringUtils.EMPTY;
  267.     }

  268.     /** The data source to connect to the database. */
  269.     private DataSource dataSource;

  270.     /** The configurationName of the table containing the configurations. */
  271.     private String table;

  272.     /** The column containing the configurationName of the configuration. */
  273.     private String configurationNameColumn;

  274.     /** The column containing the keys. */
  275.     private String keyColumn;

  276.     /** The column containing the values. */
  277.     private String valueColumn;

  278.     /** The configurationName of the configuration. */
  279.     private String configurationName;

  280.     /** A flag whether commits should be performed by this configuration. */
  281.     private boolean autoCommit;

  282.     /**
  283.      * Creates a new instance of {@code DatabaseConfiguration}.
  284.      */
  285.     public DatabaseConfiguration() {
  286.         initLogger(new ConfigurationLogger(DatabaseConfiguration.class));
  287.         addErrorLogListener();
  288.     }

  289.     /**
  290.      * Adds a property to this configuration. If this causes a database error, an error event will be generated of type
  291.      * {@code ADD_PROPERTY} with the causing exception. The event's {@code propertyName} is set to the passed in property
  292.      * key, the {@code propertyValue} points to the passed in value.
  293.      *
  294.      * @param key the property key
  295.      * @param obj the value of the property to add
  296.      */
  297.     @Override
  298.     protected void addPropertyDirect(final String key, final Object obj) {
  299.         new AbstractJdbcOperation<Void>(ConfigurationErrorEvent.WRITE, ConfigurationEvent.ADD_PROPERTY, key, obj) {
  300.             @Override
  301.             protected Void performOperation() throws SQLException {
  302.                 final StringBuilder query = new StringBuilder("INSERT INTO ");
  303.                 query.append(table).append(" (");
  304.                 query.append(keyColumn).append(", ");
  305.                 query.append(valueColumn);
  306.                 if (configurationNameColumn != null) {
  307.                     query.append(", ").append(configurationNameColumn);
  308.                 }
  309.                 query.append(") VALUES (?, ?");
  310.                 if (configurationNameColumn != null) {
  311.                     query.append(", ?");
  312.                 }
  313.                 query.append(")");

  314.                 try (PreparedStatement pstmt = initStatement(query.toString(), false, key, String.valueOf(obj))) {
  315.                     if (configurationNameColumn != null) {
  316.                         pstmt.setString(3, configurationName);
  317.                     }

  318.                     pstmt.executeUpdate();
  319.                     return null;
  320.                 }
  321.             }
  322.         }.execute();
  323.     }

  324.     /**
  325.      * Adds a property to this configuration. This implementation temporarily disables list delimiter parsing, so that even
  326.      * if the value contains the list delimiter, only a single record is written into the managed table. The implementation
  327.      * of {@code getProperty()} takes care about delimiters. So list delimiters are fully supported by
  328.      * {@code DatabaseConfiguration}, but internally treated a bit differently.
  329.      *
  330.      * @param key the key of the new property
  331.      * @param value the value to be added
  332.      */
  333.     @Override
  334.     protected void addPropertyInternal(final String key, final Object value) {
  335.         final ListDelimiterHandler oldHandler = getListDelimiterHandler();
  336.         try {
  337.             // temporarily disable delimiter parsing
  338.             setListDelimiterHandler(DisabledListDelimiterHandler.INSTANCE);
  339.             super.addPropertyInternal(key, value);
  340.         } finally {
  341.             setListDelimiterHandler(oldHandler);
  342.         }
  343.     }

  344.     /**
  345.      * Removes all entries from this configuration. If this causes a database error, an error event will be generated of
  346.      * type {@code CLEAR} with the causing exception. Both the event's {@code propertyName} and the {@code propertyValue}
  347.      * will be undefined.
  348.      */
  349.     @Override
  350.     protected void clearInternal() {
  351.         new AbstractJdbcOperation<Void>(ConfigurationErrorEvent.WRITE, ConfigurationEvent.CLEAR, null, null) {
  352.             @Override
  353.             protected Void performOperation() throws SQLException {
  354.                 try (PreparedStatement statement = initStatement(String.format(SQL_CLEAR, table), true)) {
  355.                     statement.executeUpdate();
  356.                 }
  357.                 return null;
  358.             }
  359.         }.execute();
  360.     }

  361.     /**
  362.      * Removes the specified value from this configuration. If this causes a database error, an error event will be
  363.      * generated of type {@code CLEAR_PROPERTY} with the causing exception. The event's {@code propertyName} will be set to
  364.      * the passed in key, the {@code propertyValue} will be undefined.
  365.      *
  366.      * @param key the key of the property to be removed
  367.      */
  368.     @Override
  369.     protected void clearPropertyDirect(final String key) {
  370.         new AbstractJdbcOperation<Void>(ConfigurationErrorEvent.WRITE, ConfigurationEvent.CLEAR_PROPERTY, key, null) {
  371.             @Override
  372.             protected Void performOperation() throws SQLException {
  373.                 try (PreparedStatement ps = initStatement(String.format(SQL_CLEAR_PROPERTY, table, keyColumn), true, key)) {
  374.                     ps.executeUpdate();
  375.                     return null;
  376.                 }
  377.             }
  378.         }.execute();
  379.     }

  380.     /**
  381.      * Close the specified database objects. Avoid closing if null and hide any SQLExceptions that occur.
  382.      *
  383.      * @param conn The database connection to close
  384.      * @param stmt The statement to close
  385.      * @param rs the result set to close
  386.      */
  387.     protected void close(final Connection conn, final Statement stmt, final ResultSet rs) {
  388.         try {
  389.             if (rs != null) {
  390.                 rs.close();
  391.             }
  392.         } catch (final SQLException e) {
  393.             getLogger().error("An error occurred on closing the result set", e);
  394.         }

  395.         try {
  396.             if (stmt != null) {
  397.                 stmt.close();
  398.             }
  399.         } catch (final SQLException e) {
  400.             getLogger().error("An error occurred on closing the statement", e);
  401.         }

  402.         try {
  403.             if (conn != null) {
  404.                 conn.close();
  405.             }
  406.         } catch (final SQLException e) {
  407.             getLogger().error("An error occurred on closing the connection", e);
  408.         }
  409.     }

  410.     /**
  411.      * Checks whether this configuration contains the specified key. If this causes a database error, an error event will be
  412.      * generated of type {@code READ} with the causing exception. The event's {@code propertyName} will be set to the passed
  413.      * in key, the {@code propertyValue} will be undefined.
  414.      *
  415.      * @param key the key to be checked
  416.      * @return a flag whether this key is defined
  417.      */
  418.     @Override
  419.     protected boolean containsKeyInternal(final String key) {
  420.         final AbstractJdbcOperation<Boolean> op = new AbstractJdbcOperation<Boolean>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, key, null) {
  421.             @Override
  422.             protected Boolean performOperation() throws SQLException {
  423.                 try (ResultSet rs = openResultSet(String.format(SQL_GET_PROPERTY, table, keyColumn), true, key)) {
  424.                     return rs.next();
  425.                 }
  426.             }
  427.         };

  428.         final Boolean result = op.execute();
  429.         return result != null && result.booleanValue();
  430.     }

  431.     /**
  432.      * Tests whether this configuration contains one or more matches to this value. This operation stops at first
  433.      * match but may be more expensive than the containsKey method.
  434.      * @since 2.11.0
  435.      */
  436.     @Override
  437.     protected boolean containsValueInternal(final Object value) {
  438.         final AbstractJdbcOperation<Boolean> op = new AbstractJdbcOperation<Boolean>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, null, value) {
  439.             @Override
  440.             protected Boolean performOperation() throws SQLException {
  441.                 try (ResultSet rs = openResultSet(String.format(SQL_GET_PROPERTY, table, valueColumn), false, value)) {
  442.                     return rs.next();
  443.                 }
  444.             }
  445.         };
  446.         final Boolean result = op.execute();
  447.         return result != null && result.booleanValue();
  448.     }

  449.     /**
  450.      * Extracts the value of a property from the given result set. The passed in {@code ResultSet} was created by a SELECT
  451.      * statement on the underlying database table. This implementation reads the value of the column determined by the
  452.      * {@code valueColumn} property. Normally the contained value is directly returned. However, if it is of type
  453.      * {@code CLOB}, text is extracted as string.
  454.      *
  455.      * @param rs the current {@code ResultSet}
  456.      * @return the value of the property column
  457.      * @throws SQLException if an error occurs
  458.      */
  459.     protected Object extractPropertyValue(final ResultSet rs) throws SQLException {
  460.         Object value = rs.getObject(valueColumn);
  461.         if (value instanceof Clob) {
  462.             value = convertClob((Clob) value);
  463.         }
  464.         return value;
  465.     }

  466.     /**
  467.      * Gets the name of this configuration instance.
  468.      *
  469.      * @return the name of this configuration
  470.      */
  471.     public String getConfigurationName() {
  472.         return configurationName;
  473.     }

  474.     /**
  475.      * Gets the name of the table column with the configuration name.
  476.      *
  477.      * @return the name of the configuration name column
  478.      */
  479.     public String getConfigurationNameColumn() {
  480.         return configurationNameColumn;
  481.     }

  482.     /**
  483.      * Gets the used {@code DataSource} object.
  484.      *
  485.      * @return the data source
  486.      * @since 1.4
  487.      * @deprecated Use {@link #getDataSource()}
  488.      */
  489.     @Deprecated
  490.     public DataSource getDatasource() {
  491.         return dataSource;
  492.     }

  493.     /**
  494.      * Gets the {@code DataSource} for obtaining database connections.
  495.      *
  496.      * @return the {@code DataSource}
  497.      */
  498.     public DataSource getDataSource() {
  499.         return dataSource;
  500.     }

  501.     /**
  502.      * Gets the name of the column containing the configuration keys.
  503.      *
  504.      * @return the name of the key column
  505.      */
  506.     public String getKeyColumn() {
  507.         return keyColumn;
  508.     }

  509.     /**
  510.      * Returns an iterator with the names of all properties contained in this configuration. If this causes a database
  511.      * error, an error event will be generated of type {@code READ} with the causing exception. Both the event's
  512.      * {@code propertyName} and the {@code propertyValue} will be undefined.
  513.      *
  514.      * @return an iterator with the contained keys (an empty iterator in case of an error)
  515.      */
  516.     @Override
  517.     protected Iterator<String> getKeysInternal() {
  518.         final Collection<String> keys = new ArrayList<>();
  519.         new AbstractJdbcOperation<Collection<String>>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, null, null) {
  520.             @Override
  521.             protected Collection<String> performOperation() throws SQLException {
  522.                 try (ResultSet rs = openResultSet(String.format(SQL_GET_KEYS, keyColumn, table), true)) {
  523.                     while (rs.next()) {
  524.                         keys.add(rs.getString(1));
  525.                     }
  526.                     return keys;
  527.                 }
  528.             }
  529.         }.execute();

  530.         return keys.iterator();
  531.     }

  532.     /**
  533.      * Gets the value of the specified property. If this causes a database error, an error event will be generated of
  534.      * type {@code READ} with the causing exception. The event's {@code propertyName} is set to the passed in property key,
  535.      * the {@code propertyValue} is undefined.
  536.      *
  537.      * @param key the key of the desired property
  538.      * @return the value of this property
  539.      */
  540.     @Override
  541.     protected Object getPropertyInternal(final String key) {
  542.         final AbstractJdbcOperation<Object> op = new AbstractJdbcOperation<Object>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, key, null) {
  543.             @Override
  544.             protected Object performOperation() throws SQLException {
  545.                 final List<Object> results = new ArrayList<>();
  546.                 try (ResultSet rs = openResultSet(String.format(SQL_GET_PROPERTY, table, keyColumn), true, key)) {
  547.                     while (rs.next()) {
  548.                         // Split value if it contains the list delimiter
  549.                         getListDelimiterHandler().parse(extractPropertyValue(rs)).forEach(results::add);
  550.                     }
  551.                 }
  552.                 if (!results.isEmpty()) {
  553.                     return results.size() > 1 ? results : results.get(0);
  554.                 }
  555.                 return null;
  556.             }
  557.         };

  558.         return op.execute();
  559.     }

  560.     /**
  561.      * Gets the name of the table containing configuration data.
  562.      *
  563.      * @return the name of the table to be queried
  564.      */
  565.     public String getTable() {
  566.         return table;
  567.     }

  568.     /**
  569.      * Gets the name of the column containing the configuration values.
  570.      *
  571.      * @return the name of the value column
  572.      */
  573.     public String getValueColumn() {
  574.         return valueColumn;
  575.     }

  576.     /**
  577.      * Returns a flag whether this configuration performs commits after database updates.
  578.      *
  579.      * @return a flag whether commits are performed
  580.      */
  581.     public boolean isAutoCommit() {
  582.         return autoCommit;
  583.     }

  584.     /**
  585.      * Checks if this configuration is empty. If this causes a database error, an error event will be generated of type
  586.      * {@code READ} with the causing exception. Both the event's {@code propertyName} and {@code propertyValue} will be
  587.      * undefined.
  588.      *
  589.      * @return a flag whether this configuration is empty.
  590.      */
  591.     @Override
  592.     protected boolean isEmptyInternal() {
  593.         final AbstractJdbcOperation<Integer> op = new AbstractJdbcOperation<Integer>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, null, null) {
  594.             @Override
  595.             protected Integer performOperation() throws SQLException {
  596.                 try (ResultSet rs = openResultSet(String.format(SQL_IS_EMPTY, table), true)) {
  597.                     return rs.next() ? Integer.valueOf(rs.getInt(1)) : null;
  598.                 }
  599.             }
  600.         };

  601.         final Integer count = op.execute();
  602.         return count == null || count.intValue() == 0;
  603.     }

  604.     /**
  605.      * Sets the auto commit flag. If set to <strong>true</strong>, this configuration performs a commit after each database update.
  606.      *
  607.      * @param autoCommit the auto commit flag
  608.      */
  609.     public void setAutoCommit(final boolean autoCommit) {
  610.         this.autoCommit = autoCommit;
  611.     }

  612.     /**
  613.      * Sets the name of this configuration instance.
  614.      *
  615.      * @param configurationName the name of this configuration
  616.      */
  617.     public void setConfigurationName(final String configurationName) {
  618.         this.configurationName = configurationName;
  619.     }

  620.     /**
  621.      * Sets the name of the table column with the configuration name.
  622.      *
  623.      * @param configurationNameColumn the name of the column with the configuration name
  624.      */
  625.     public void setConfigurationNameColumn(final String configurationNameColumn) {
  626.         this.configurationNameColumn = configurationNameColumn;
  627.     }

  628.     /**
  629.      * Sets the {@code DataSource} for obtaining database connections.
  630.      *
  631.      * @param dataSource the {@code DataSource}
  632.      */
  633.     public void setDataSource(final DataSource dataSource) {
  634.         this.dataSource = dataSource;
  635.     }

  636.     /**
  637.      * Sets the name of the column containing the configuration keys.
  638.      *
  639.      * @param keyColumn the name of the key column
  640.      */
  641.     public void setKeyColumn(final String keyColumn) {
  642.         this.keyColumn = keyColumn;
  643.     }

  644.     /**
  645.      * Sets the name of the table containing configuration data.
  646.      *
  647.      * @param table the table name
  648.      */
  649.     public void setTable(final String table) {
  650.         this.table = table;
  651.     }

  652.     /**
  653.      * Sets the name of the column containing the configuration values.
  654.      *
  655.      * @param valueColumn the name of the value column
  656.      */
  657.     public void setValueColumn(final String valueColumn) {
  658.         this.valueColumn = valueColumn;
  659.     }
  660. }