/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.jdbc.connections;

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLRecoverableException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.jena.graph.Triple ;
import org.apache.jena.jdbc.JdbcCompatibility;
import org.apache.jena.jdbc.metadata.JenaMetadata;
import org.apache.jena.jdbc.postprocessing.ResultsPostProcessor;
import org.apache.jena.jdbc.preprocessing.CommandPreProcessor;
import org.apache.jena.jdbc.results.metadata.AskResultsMetadata;
import org.apache.jena.jdbc.results.metadata.SelectResultsMetadata;
import org.apache.jena.jdbc.results.metadata.TripleResultsMetadata;
import org.apache.jena.jdbc.statements.JenaPreparedStatement;
import org.apache.jena.jdbc.statements.JenaStatement;
import org.apache.jena.query.Query ;
import org.apache.jena.update.UpdateRequest ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract base implementation of a Jena JDBC connection
 * <p>
 * Generally speaking this is a faithful implementation of a JDBC connection but
 * it also provides a couple of Jena JDBC specific features:
 * </p>
 * <ol>
 * <li>JDBC compatibility level</li>
 * <li>Command pre-processors</li>
 * </ol>
 * <p>
 * The JDBC compatibility level allows the API to behave slightly differently
 * depending on how JDBC like you need it to be, see {@link JdbcCompatibility}
 * for more discussion on this.
 * </p>
 * <p>
 * Command pre-processors are an extension mechanism designed to allow Jena JDBC
 * connections to cope with the fact that the tools consuming the API may be
 * completely unaware that we speak SPARQL rather than SQL. They allow for
 * manipulation of incoming command text as well as manipulation of the parsed
 * SPARQL queries and updates as desired.
 * </p>
 */
public abstract class JenaConnection implements Connection {

    private static final Logger LOGGER = LoggerFactory.getLogger(JenaConnection.class);

    /**
     * Constant for default cursor holdability for Jena JDBC connections
     */
    public static final int DEFAULT_HOLDABILITY = ResultSet.CLOSE_CURSORS_AT_COMMIT;
    /**
     * Constant for default auto-commit for Jena JDBC connections
     */
    public final static boolean DEFAULT_AUTO_COMMIT = true;
    /**
     * Constant for default transaction isolation level for Jena JDBC
     * connections
     */
    public final static int DEFAULT_ISOLATION_LEVEL = TRANSACTION_NONE;

    private Properties clientInfo = new Properties();
    private int holdability = DEFAULT_HOLDABILITY;
    private SQLWarning warnings = null;
    private boolean autoCommit = DEFAULT_AUTO_COMMIT;
    private int isolationLevel = DEFAULT_ISOLATION_LEVEL;
    private int compatibilityLevel = JdbcCompatibility.DEFAULT;
    private List<Statement> statements = new ArrayList<>();
    private List<CommandPreProcessor> preProcessors = new ArrayList<>();
    private List<ResultsPostProcessor> postProcessors = new ArrayList<>();

    /**
     * Creates a new connection
     * 
     * @param holdability
     *            Cursor holdability
     * @param autoCommit
     *            Sets auto-commit behaviour for the connection
     * @param transactionLevel
     *            Sets transaction isolation level for the connection
     * @param compatibilityLevel
     *            Sets JDBC compatibility level for the connection, see
     *            {@link JdbcCompatibility}
     * @throws SQLException
     *             Thrown if the arguments are invalid
     */
    public JenaConnection(int holdability, boolean autoCommit, int transactionLevel, int compatibilityLevel) throws SQLException {
        this.checkHoldability(holdability);
        this.holdability = holdability;
        this.setAutoCommit(autoCommit);
        this.setTransactionIsolation(transactionLevel);
        this.compatibilityLevel = JdbcCompatibility.normalizeLevel(compatibilityLevel);
    }

    /**
     * Gets the JDBC compatibility level that is in use, see
     * {@link JdbcCompatibility} for explanations
     * 
     * @return Compatibility level
     */
    public int getJdbcCompatibilityLevel() {
        return this.compatibilityLevel;
    }

    /**
     * Sets the JDBC compatibility level that is in use, see
     * {@link JdbcCompatibility} for explanations.
     * <p>
     * Changing the level may not effect existing open objects, behaviour in
     * this case will be implementation specific.
     * </p>
     * 
     * @param level
     *            Compatibility level
     */
    public void setJdbcCompatibilityLevel(int level) {
        this.compatibilityLevel = JdbcCompatibility.normalizeLevel(level);
    }

    /**
     * Adds a command pre-processor to the connection
     * 
     * @param preProcessor
     *            Pre-processor to add
     */
    public final void addPreProcessor(CommandPreProcessor preProcessor) {
        if (preProcessor == null)
            return;
        this.preProcessors.add(preProcessor);
    }

    /**
     * Adds a results post-processor to the connection
     * 
     * @param postProcessor
     *            Post-processor to add
     */
    public final void addPostProcessor(ResultsPostProcessor postProcessor) {
        if (postProcessor == null)
            return;
        this.postProcessors.add(postProcessor);
    }

    /**
     * Inserts a command pre-processor for the connection
     * 
     * @param index
     *            Index to insert at
     * @param preProcessor
     *            Pre-processor
     */
    public final void insertPreProcessor(int index, CommandPreProcessor preProcessor) {
        if (preProcessor == null)
            return;
        this.preProcessors.add(index, preProcessor);
    }

    /**
     * Inserts a results post-processor for the connection
     * 
     * @param index
     *            Index to insert at
     * @param postProcessor
     *            Post-processor
     */
    public final void insertPostProcessor(int index, ResultsPostProcessor postProcessor) {
        if (postProcessor == null)
            return;
        this.postProcessors.add(index, postProcessor);
    }

    /**
     * Removes a command pre-processor from the connection
     * 
     * @param preProcessor
     *            Pre-processor to remove
     */
    public final void removePreProcessor(CommandPreProcessor preProcessor) {
        if (preProcessor == null)
            return;
        this.preProcessors.remove(preProcessor);
    }

    /**
     * Removes a results post-processor from the connection
     * 
     * @param postProcessor
     *            Post-processor to remove
     */
    public final void removePostProcessor(ResultsPostProcessor postProcessor) {
        if (postProcessor == null)
            return;
        this.postProcessors.remove(postProcessor);
    }

    /**
     * Removes a command pre-processor from the connection
     * 
     * @param index
     *            Index to remove at
     */
    public final void removePreProcessor(int index) {
        this.preProcessors.remove(index);
    }

    /**
     * Removes a results post-processor from the connection
     * 
     * @param index
     *            Index to remove at
     */
    public final void removePostProcessor(int index) {
        this.postProcessors.remove(index);
    }

    /**
     * Clears all command pre-processor from the connection
     */
    public final void clearPreProcessors() {
        this.preProcessors.clear();
    }

    /**
     * Clears all command post-processors from the connection
     */
    public final void clearPostProcessors() {
        this.postProcessors.clear();
    }

    /**
     * Gets the currently registered pre-processors for the connection
     * 
     * @return Iterator of pre-processors
     */
    public final Iterator<CommandPreProcessor> getPreProcessors() {
        return this.preProcessors.iterator();
    }

    /**
     * Gets the currently registered post-processors for the connection
     * 
     * @return Iterator of post-processors
     */
    public final Iterator<ResultsPostProcessor> getPostProcessors() {
        return this.postProcessors.iterator();
    }

    /**
     * Apply registered pre-processors to the given command text
     * 
     * @param text
     *            Command Text
     * @return Command Text after processing by registered pre-processors
     * @throws SQLException
     */
    public final String applyPreProcessors(String text) throws SQLException {
        for (CommandPreProcessor preProcessor : this.preProcessors) {
            if (preProcessor == null)
                continue;
            text = preProcessor.preProcessCommandText(text);
        }
        return text;
    }

    /**
     * Applies registered pre-processors to the given query
     * 
     * @param q
     *            Query
     * @return Query after processing by registered pre-processors
     * @throws SQLException
     */
    public final Query applyPreProcessors(Query q) throws SQLException {
        for (CommandPreProcessor preProcessor : this.preProcessors) {
            if (preProcessor == null)
                continue;
            q = preProcessor.preProcessQuery(q);
        }
        return q;
    }

    /**
     * Applies registered pre-processors to the given update
     * 
     * @param u
     *            Update
     * @return Update after processing by registered pre-processors
     * @throws SQLException
     */
    public final UpdateRequest applyPreProcessors(UpdateRequest u) throws SQLException {
        for (CommandPreProcessor preProcessor : this.preProcessors) {
            if (preProcessor == null)
                continue;
            u = preProcessor.preProcessUpdate(u);
        }
        return u;
    }

    /**
     * Applies registered post-processors to the given results
     * 
     * @param results
     *            Results
     * @return Results after processing by registered post-processors
     * @throws SQLException
     */
    public final org.apache.jena.query.ResultSet applyPostProcessors(org.apache.jena.query.ResultSet results) throws SQLException {
        for (ResultsPostProcessor postProcessor : this.postProcessors) {
            if (postProcessor == null)
                continue;
            results = postProcessor.postProcessResults(results);
        }
        return results;
    }

    /**
     * Applies registered post-processors to the given results
     * 
     * @param triples
     *            Results
     * @return Results after processing by registered post-processors
     * @throws SQLException
     */
    public final Iterator<Triple> applyPostProcessors(Iterator<Triple> triples) throws SQLException {
        for (ResultsPostProcessor postProcessor : this.postProcessors) {
            if (postProcessor == null)
                continue;
            triples = postProcessor.postProcessResults(triples);
        }
        return triples;
    }

    /**
     * Applies registered post-processors to the given results
     * 
     * @param result
     *            Result
     * @return Result after processing by registered post-processors
     * @throws SQLException
     */
    public final boolean applyPostProcessors(boolean result) throws SQLException {
        for (ResultsPostProcessor postProcessor : this.postProcessors) {
            if (postProcessor == null)
                continue;
            result = postProcessor.postProcessResults(result);
        }
        return result;
    }

    /**
     * Applies registered post-processors to the given results metadata
     * 
     * @param metadata
     *            Results metadata
     * @return Results metadata after processing by registered post-processors
     * @throws SQLException
     */
    public final SelectResultsMetadata applyPostProcessors(SelectResultsMetadata metadata) throws SQLException {
        for (ResultsPostProcessor postProcessor : this.postProcessors) {
            if (postProcessor == null)
                continue;
            metadata = postProcessor.postProcessResultsMetadata(metadata);
        }
        return metadata;
    }

    /**
     * Applies registered post-processors to the given results metadata
     * 
     * @param metadata
     *            Results metadata
     * @return Results metadata after processing by registered post-processors
     * @throws SQLException
     */
    public final TripleResultsMetadata applyPostProcessors(TripleResultsMetadata metadata) throws SQLException {
        for (ResultsPostProcessor postProcessor : this.postProcessors) {
            if (postProcessor == null)
                continue;
            metadata = postProcessor.postProcessResultsMetadata(metadata);
        }
        return metadata;
    }

    /**
     * Applies registered post-processors to the given results metadata
     * 
     * @param metadata
     *            Results metadata
     * @return Results metadata after processing by registered post-processors
     * @throws SQLException
     */
    public final AskResultsMetadata applyPostProcessors(AskResultsMetadata metadata) throws SQLException {
        for (ResultsPostProcessor postProcessor : this.postProcessors) {
            if (postProcessor == null)
                continue;
            metadata = postProcessor.postProcessResultsMetadata(metadata);
        }
        return metadata;
    }

    @Override
    public boolean isWrapperFor(Class<?> arg0) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public <T> T unwrap(Class<T> arg0) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void clearWarnings() {
        this.warnings = null;
    }

    @Override
    public final void close() throws SQLException {
        try {
            LOGGER.info("Closing connection...");
            // Close any open statements
            this.closeStatements();
        } finally {
            this.closeInternal();
            LOGGER.info("Connection was closed");
        }
    }

    private void closeStatements() throws SQLException {
        synchronized (this.statements) {
            if (this.statements.size() > 0) {
                LOGGER.info("Attempting to close " + this.statements.size() + " open statements");
                for (Statement stmt : this.statements) {
                    stmt.close();
                }
                LOGGER.info("All open statements were closed");
                this.statements.clear();
            }
        }
    }

    protected abstract void closeInternal() throws SQLException;

    @Override
    public void commit() throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot commit on a closed connection");
        try {
            LOGGER.info("Attempting to commit a transaction...");

            // Get the implementation to do the actual commit
            this.commitInternal();

            LOGGER.info("Transaction was committed");

            // If applicable close cursors
            if (this.holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT) {
                LOGGER.info("Holdability set to CLOSE_CURSORS_AT_COMMIT so closing open statements");
                this.closeStatements();
            }
        } catch (SQLException e) {
            // Throw as-is
            throw e;
        } catch (Exception e) {
            // Wrap as SQLException
            LOGGER.error("Unexpected error in transaction commit", e);
            throw new SQLException("Unexpected error committing transaction", e);
        }
    }

    protected abstract void commitInternal() throws SQLException;

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public Blob createBlob() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public Clob createClob() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public NClob createNClob() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public final Statement createStatement() throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot create a statement after the connection was closed");
        return this.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    }

    @Override
    public final Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot create a statement after the connection was closed");
        return this.createStatement(resultSetType, resultSetConcurrency, this.getHoldability());
    }

    @Override
    public final Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot create a statement after the connection was closed");
        JenaStatement stmt = this.createStatementInternal(resultSetType, resultSetConcurrency, resultSetHoldability);
        synchronized (this.statements) {
            this.statements.add(stmt);
        }
        return stmt;
    }

    protected abstract JenaStatement createStatementInternal(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
            throws SQLException;

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public boolean getAutoCommit() {
        return this.autoCommit;
    }

    @Override
    public String getCatalog() {
        return JenaMetadata.DEFAULT_CATALOG;
    }

    @Override
    public Properties getClientInfo() {
        return this.clientInfo;
    }

    @Override
    public String getClientInfo(String name) {
        return this.clientInfo.getProperty(name);
    }

    @Override
    public int getHoldability() {
        return this.holdability;
    }

    @Override
    public abstract DatabaseMetaData getMetaData() throws SQLException;

    @Override
    public int getTransactionIsolation() {
        return this.isolationLevel;
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public SQLWarning getWarnings() {
        return this.warnings;
    }

    @Override
    public abstract boolean isClosed() throws SQLException;

    @Override
    public abstract boolean isReadOnly() throws SQLException;

    @Override
    public abstract boolean isValid(int timeout) throws SQLException;

    @Override
    public String nativeSQL(String sql) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot create a statement after the connection was closed");
        return this.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot create a statement after the connection was closed");
        return this.prepareStatement(sql);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot create a statement after the connection was closed");
        return this.prepareStatement(sql);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot create a statement after the connection was closed");
        return this.prepareStatement(sql);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot create a statement after the connection was closed");
        return this.prepareStatement(sql, resultSetType, resultSetConcurrency, this.holdability);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot create a statement after the connection was closed");
        return this.createPreparedStatementInternal(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
    }

    /**
     * Helper method which derived implementations must implement to provide an
     * actual prepared statement implementation
     * 
     * @param sql
     *            SPARQL command
     * @param resultSetType
     *            Desired result set type
     * @param resultSetConcurrency
     *            Result set concurrency
     * @param resultSetHoldability
     *            Result set holdability
     * @return Prepared statement
     * @throws SQLException
     */
    protected abstract JenaPreparedStatement createPreparedStatementInternal(String sql, int resultSetType,
            int resultSetConcurrency, int resultSetHoldability) throws SQLException;

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void rollback() throws SQLException {
        if (this.isClosed())
            throw new SQLException("Cannot rollback on a closed connection");
        try {
            LOGGER.info("Attempting to rollback a transaction...");

            // Get the implementation to do the actual rollback
            this.rollbackInternal();

            LOGGER.info("Transaction was rolled back");

            // Close any open statements if applicable
            if (this.holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT) {
                LOGGER.info("Holdability is set to CLOSE_CURSORS_AT_COMMIT so closing open statements");
                this.closeStatements();
            }
        } catch (SQLException e) {
            // Throw as-is
            throw e;
        } catch (Exception e) {
            throw new SQLException("Unexpected error rolling back transaction", e);
        }
    }

    protected abstract void rollbackInternal() throws SQLException;

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setAutoCommit(boolean autoCommit) {
        this.autoCommit = autoCommit;
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setClientInfo(Properties properties) {
        this.clientInfo = properties;
    }

    @Override
    public void setClientInfo(String name, String value) {
        this.clientInfo.put(name, value);
    }

    @Override
    public void setHoldability(int holdability) throws SQLException {
        this.checkHoldability(holdability);
        this.holdability = holdability;
    }

    /**
     * Helper method that checks whether the given holdability setting is valid,
     * throws an error if it is not
     * 
     * @param h
     *            Holdability Setting
     * @throws SQLException
     *             Thrown if the setting is not valid
     */
    protected void checkHoldability(int h) throws SQLException {
        switch (h) {
        case ResultSet.CLOSE_CURSORS_AT_COMMIT:
        case ResultSet.HOLD_CURSORS_OVER_COMMIT:
            return;
        default:
            throw new SQLRecoverableException(String.format("%d is not a valid holdability setting", h));
        }
    }

    @Override
    public abstract void setReadOnly(boolean readOnly) throws SQLException;

    @Override
    public Savepoint setSavepoint() throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
        this.checkTransactionIsolation(level);
        this.isolationLevel = level;
    }

    /**
     * Helper method which checks that a transaction isolation level is valid,
     * should throw an exception if the given level is not valid for the
     * connection
     * 
     * @param level
     *            Isolation Level
     * @throws SQLException
     *             Thrown if the isolation level is not valid
     */
    protected abstract void checkTransactionIsolation(int level) throws SQLException;

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    /**
     * Helper method that derived classes may use to set warnings
     * 
     * @param warning
     *            Warning
     */
    protected void setWarning(SQLWarning warning) {
        LOGGER.warn("SQL Warning was issued", warning);

        if (this.warnings == null) {
            this.warnings = warning;
        } else {
            // Chain with existing warnings
            warning.setNextWarning(this.warnings);
            this.warnings = warning;
        }
    }

    /**
     * Helper method that derived classes may use to set warnings
     * 
     * @param warning
     *            Warning
     */
    protected void setWarning(String warning) {
        this.setWarning(new SQLWarning(warning));
    }

    /**
     * Helper method that derived classes may use to set warnings
     * 
     * @param warning
     *            Warning
     * @param cause
     *            Cause
     */
    protected void setWarning(String warning, Throwable cause) {
        this.setWarning(new SQLWarning(warning, cause));
    }

	//--- Java6/7 compatibility.
	public void setSchema(String schema) throws SQLException {
		throw new SQLFeatureNotSupportedException();	
	}

	public String getSchema() throws SQLException {
		throw new SQLFeatureNotSupportedException();	
	}

	public void abort(java.util.concurrent.Executor executor) throws SQLException {
		throw new SQLFeatureNotSupportedException();	
	}

	public int getNetworkTimeout() throws SQLException {
		throw new SQLFeatureNotSupportedException();	
	}

	public void setNetworkTimeout(java.util.concurrent.Executor executor, int milliseconds) throws SQLException {
		throw new SQLFeatureNotSupportedException();	
	}
}
