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