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}