001package net.sf.jkniv.whinstone.jdbc;
002
003import java.sql.Connection;
004import java.sql.DatabaseMetaData;
005import java.sql.PreparedStatement;
006import java.sql.SQLException;
007import java.sql.Statement;
008
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012import net.sf.jkniv.asserts.Assertable;
013import net.sf.jkniv.asserts.AssertsFactory;
014import net.sf.jkniv.exception.HandleableException;
015import net.sf.jkniv.sqlegance.Insertable;
016import net.sf.jkniv.sqlegance.RepositoryException;
017import net.sf.jkniv.sqlegance.Sql;
018import net.sf.jkniv.sqlegance.dialect.SqlDialect;
019import net.sf.jkniv.sqlegance.dialect.SqlFeatureSupport;
020import net.sf.jkniv.whinstone.ConnectionAdapter;
021import net.sf.jkniv.whinstone.Queryable;
022import net.sf.jkniv.whinstone.ResultRow;
023import net.sf.jkniv.whinstone.commands.Command;
024import net.sf.jkniv.whinstone.jdbc.commands.AddAutoKeyJdbcCommand;
025import net.sf.jkniv.whinstone.jdbc.commands.AddSequenceKeyJdbcCommand;
026import net.sf.jkniv.whinstone.jdbc.commands.BulkJdbcCommand;
027import net.sf.jkniv.whinstone.jdbc.commands.DefaultJdbcCommand;
028import net.sf.jkniv.whinstone.jdbc.commands.DefaultJdbcQuery;
029import net.sf.jkniv.whinstone.jdbc.commands.JdbcAutoGeneratedKey;
030import net.sf.jkniv.whinstone.jdbc.commands.JdbcSequenceGeneratedKey;
031import net.sf.jkniv.whinstone.jdbc.statement.JdbcPreparedStatementAdapter;
032import net.sf.jkniv.whinstone.statement.AutoKey;
033import net.sf.jkniv.whinstone.statement.StatementAdapter;
034import net.sf.jkniv.whinstone.transaction.TransactionContext;
035import net.sf.jkniv.whinstone.transaction.TransactionSessions;
036import net.sf.jkniv.whinstone.types.RegisterType;
037
038public class JdbcConnectionAdapter implements ConnectionAdapter
039{
040    private static final Logger SQLLOG = net.sf.jkniv.whinstone.jdbc.LoggerFactory.getLogger();
041    private static final Logger LOG = LoggerFactory.getLogger(JdbcConnectionAdapter.class);
042    private static final Assertable NOT_NULL = AssertsFactory.getNotNull();
043    private final Connection          conn;
044    private final String              contextName;
045    private final HandleableException handlerException;
046    
047    public JdbcConnectionAdapter(String contextName, Connection conn, HandleableException handlerException)
048    {
049        NOT_NULL.verify(conn, contextName);
050        this.contextName = contextName;
051        this.conn = conn;
052        this.handlerException = handlerException;
053    }
054    
055    @Override
056    public String getContextName()
057    {
058        return this.contextName;
059    }
060    
061    @Override
062    public void commit() throws SQLException
063    {
064        //        try
065        //        {
066        this.conn.commit();
067        //        }
068        //        catch (SQLException sqle)
069        //        {
070        //            handlerException.handle(sqle, "COMMIT");
071        //        }
072    }
073    
074    @Override
075    public void rollback() throws SQLException
076    {
077        //        try
078        //        {
079        this.conn.rollback();
080        //        }
081        //        catch (SQLException sqle)
082        //        {
083        //            handlerException.handle(sqle, "ROLLBACK");
084        //        }        
085    }
086    
087    @Override
088    public void close() //throws SQLException
089    {
090        try
091        {
092            TransactionContext ctx = TransactionSessions.get(contextName);
093            if (ctx == null || !ctx.isActive())
094                this.conn.close();
095        }
096        catch (SQLException sqle)
097        {
098            throw new RepositoryException("Cannot close connection [" + sqle.getMessage() + "]", sqle);
099            //LOG.warn("Erro to closing connection. Reason: " + sqle.getMessage());
100        }
101    }
102    
103    @Override
104    public boolean isClosed() throws SQLException
105    {
106        //        boolean closed = false;
107        //        try
108        //        {
109        return conn.isClosed();
110        //        }
111        //        catch (SQLException sqle)
112        //        {
113        //            LOG.warn("Erro to check if connection is closed. Reason: " + sqle.getMessage());
114        //        }
115        //        return closed;
116    }
117    
118    @Override
119    public boolean isAutoCommit() throws SQLException
120    {
121        //        boolean isAuto = false;
122        //        try
123        //        {
124        return conn.getAutoCommit();
125        //        }
126        //        catch (SQLException sqle)
127        //        {
128        //            LOG.warn("Erro to check if connection is auto-commit. Reason: " + sqle.getMessage());
129        //        }
130        //        return isAuto;
131    }
132    
133    @Override
134    public void autoCommitOn() throws SQLException
135    {
136        //        boolean autoOnChanged = false;
137        //        try
138        //        {
139        conn.setAutoCommit(true);
140        //            autoOnChanged = true;
141        //        }
142        //        catch (SQLException sqle)
143        //        {
144        //            LOG.error("Erro to change connection to auto-commit[true]. Reason: " + sqle.getMessage());
145        //        }
146        //        return autoOnChanged;
147    }
148    
149    @Override
150    public void autoCommitOff() throws SQLException
151    {
152        //        boolean autoOnChanged = false;
153        //        try
154        //        {
155        conn.setAutoCommit(false);
156        //            autoOnChanged = true;
157        //        }
158        //        catch (SQLException sqle)
159        //        {
160        //            LOG.error("Erro to change connection to auto-commit[false]. Reason: " + sqle.getMessage());
161        //        }
162        //        return autoOnChanged;
163    }
164    
165    //@Override
166    @SuppressWarnings({ "rawtypes", "unchecked" })
167    private <T, R> StatementAdapter<T, R> newStatement(Queryable queryable)
168    {
169        PreparedStatement stmt = prepareStatement(queryable);
170        StatementAdapter<T, R> adapter = new JdbcPreparedStatementAdapter(stmt, queryable);
171        return adapter;
172    }
173    
174    @SuppressWarnings({ "unchecked", "rawtypes" })
175    private <T, R> StatementAdapter<T, R> newInsertStatement(Queryable queryable)
176    {
177        Insertable isql = queryable.getDynamicSql().asInsertable();
178        PreparedStatement stmt = null;
179        AutoKey auto = null;
180        if (isql.isAutoGenerateKey() && isql.getAutoGeneratedKey().isAutoStrategy())
181        {
182            String[] columns = isql.getAutoGeneratedKey().getColumnsAsArray();
183            stmt = prepareStatement(queryable, columns);
184            auto = new JdbcAutoGeneratedKey(stmt, handlerException);
185        }
186        else
187        {
188            stmt = prepareStatement(queryable);
189            auto = new JdbcSequenceGeneratedKey(isql, conn, handlerException);
190
191        }
192        StatementAdapter<T, R> adapter = new JdbcPreparedStatementAdapter(stmt, queryable);
193        adapter.with(auto);
194        return adapter;
195    }
196    
197    /*
198     * Creates a {@code PreparedStatement} object object capable of returning the auto-generated 
199     * keys designated by the given array.
200     * 
201     * @param conn Opened connection to database
202     * @param queryable query object with parameters
203     * @param columnNames an array of column names indicating the columns that should be returned from the inserted row or rows 
204     * @return a new PreparedStatement object, containing the pre-compiled SQL statement.
205     * @throws net.sf.jkniv.sqlegance.RepositoryException wrapper SQLException
206     * @see java.sql.SQLException
207     *
208    private PreparedStatement prepareStatement(Connection conn, Queryable queryable, String[] columnNames)
209    {
210        PreparedStatement stmt = null;
211        Sql isql = queryable.getDynamicSql();
212        String[] paramsNames = queryable.getParamsNames();
213        String query = queryable.query();
214        try
215        {
216            if (LOG.isTraceEnabled())
217                LOG.trace("Preparing SQL statement type with [{}] column names", paramsNames.length);
218            
219            SQLLOG.info(query);
220            if (columnNames.length > 0)
221                stmt = conn.prepareStatement(query, columnNames);
222            else
223                stmt = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
224            
225            if (isql.getTimeout() > 0)
226                stmt.setQueryTimeout(isql.getTimeout());
227        }
228        catch (SQLException sqle)
229        {
230            throw new RepositoryException("Cannot prepare statement!", sqle);
231        }
232        return stmt;
233    }
234    */
235    
236//    @Override
237//    @SuppressWarnings({ "rawtypes", "unchecked" })
238//    public <T, R> StatementAdapter<T, R> newStatement(String sql, LanguageType languageType)
239//    {
240//        PreparedStatement stmt = null;
241//        StatementAdapter<T, R> adapter = null;
242//        try
243//        {
244//            stmt = conn.prepareStatement(sql);
245//            adapter = new net.sf.jkniv.whinstone.jdbc.statement.JdbcPreparedStatementAdapter(stmt, null);
246//        }
247//        catch (SQLException sqle)
248//        {
249//            throw new RepositoryException("Cannot prepare statement to [" + sql + "]", sqle);
250//        }
251//        return adapter;
252//    }
253    
254    @Override
255    public Object getMetaData()
256    {
257        DatabaseMetaData metadata = null;
258        try
259        {
260            metadata = conn.getMetaData();
261        }
262        catch (SQLException sqle)
263        {
264            LOG.error("Erro to read data base meta data. Reason: " + sqle.getMessage());
265        }
266        
267        return metadata;
268    }
269    
270    @Override
271    public Object unwrap()
272    {
273        return conn;
274    }
275    
276    /**
277     * Creates a PreparedStatement object that will generate ResultSet objects with the given type, concurrency, and holdability.
278     * The parameters values is setting
279     * @param conn Opened connection to database
280     * @return a new PreparedStatement object, containing the pre-compiled SQL statement.
281     * @throws net.sf.jkniv.sqlegance.RepositoryException wrapper SQLException
282     * @see java.sql.SQLException
283     */
284    private PreparedStatement prepareStatement(Queryable queryable)
285    {
286        PreparedStatement stmt = null;
287        Sql isql = queryable.getDynamicSql();
288        if (LOG.isTraceEnabled())
289            LOG.trace("Preparing SQL statement [{}] type [{}], concurrency [{}], holdability [{}] with [{}] parameters",
290                    queryable.getName(), isql.getResultSetType(), isql.getResultSetConcurrency(), isql.getResultSetHoldability(),
291                    queryable.getParamsNames().length);
292        stmt = buildNewStatement(queryable);
293        /*
294        SqlDialect dialect = queryable.getDynamicSql().getSqlDialect();
295        int rsType = isql.getResultSetType().getTypeScroll();
296        int rsConcurrency = isql.getResultSetConcurrency().getConcurrencyMode();
297        int rsHoldability = isql.getResultSetHoldability().getHoldability();
298        
299        try
300        {
301            if (queryable.getDynamicSql().isInsertable())
302            {
303                Insertable insertTag = isql.asInsertable();
304                if (insertTag.isAutoGenerateKey() && insertTag.getAutoGeneratedKey().isAutoStrategy())
305                {
306                    String[] columns = insertTag.getAutoGeneratedKey().getColumnsAsArray();
307                    stmt = conn.prepareStatement(queryable.query(), columns);
308                }
309            }
310            if (stmt == null)
311            {
312                if (dialect.supportsStmtHoldability())
313                    stmt = conn.prepareStatement(queryable.query(), rsType, rsConcurrency, rsHoldability);
314                else
315                {
316                    // SQLServer doesn't support Holdability
317                    stmt = conn.prepareStatement(queryable.query(), rsType, rsConcurrency);
318                    if (dialect.supportsConnHoldability())
319                        conn.setHoldability(rsHoldability);
320                }
321            }
322            if (isql.getTimeout() > 0)
323                stmt.setQueryTimeout(isql.getTimeout());
324        }
325        catch (SQLException sqle)
326        {
327            throw new RepositoryException("Cannot prepare statement [" + sqle.getMessage() + "]", sqle);
328        }*/
329        return stmt;
330    }
331    
332    private PreparedStatement buildNewStatement(Queryable queryable)
333    {
334        PreparedStatement stmt = null;
335        Sql isql = queryable.getDynamicSql();
336        int rsType = isql.getResultSetType().getTypeScroll();
337        int rsConcurrency = isql.getResultSetConcurrency().getConcurrencyMode();
338        int rsHoldability = isql.getResultSetHoldability().getHoldability();
339        SqlDialect sqlDialect = queryable.getDynamicSql().getSqlDialect();
340        String query = queryable.query();
341        try
342        {
343            SQLLOG.info(query);
344            if (isql.isInsertable())
345            {
346                Insertable insertTag = isql.asInsertable();
347                if (insertTag.isAutoGenerateKey() && insertTag.getAutoGeneratedKey().isAutoStrategy())
348                {
349                    String[] columns = insertTag.getAutoGeneratedKey().getColumnsAsArray();
350                    stmt = conn.prepareStatement(query, columns);
351                }
352            }
353            if (stmt == null)
354            {
355                if (sqlDialect.supportsFeature(SqlFeatureSupport.STMT_HOLDABILITY))
356                    stmt = conn.prepareStatement(query, rsType, rsConcurrency, rsHoldability);
357                else
358                {
359                    // SQLServer/Oracle12 doesn't support Holdability
360                    stmt = conn.prepareStatement(query, rsType, rsConcurrency);
361                    if (sqlDialect.supportsFeature(SqlFeatureSupport.CONN_HOLDABILITY))
362                        conn.setHoldability(rsHoldability);
363                }
364            }
365            if (isql.getTimeout() > 0)
366                stmt.setQueryTimeout(isql.getTimeout());
367        }
368        catch (SQLException sqle)
369        {
370            throw new RepositoryException("Cannot prepare statement [" + sqle.getMessage() + "]\n"+query, sqle);
371        }
372        return stmt;
373    }
374
375    /**
376     * Creates a {@code PreparedStatement} object object capable of returning the auto-generated 
377     * keys designated by the given array.
378     * 
379     * @param conn Opened connection to database
380     * @param columnNames an array of column names indicating the columns that should be returned from the inserted row or rows 
381     * @return a new PreparedStatement object, containing the pre-compiled SQL statement.
382     * @throws net.sf.jkniv.sqlegance.RepositoryException wrapper SQLException
383     * @see java.sql.SQLException
384     */
385    private PreparedStatement prepareStatement(Queryable queryable, String[] columnNames)
386    {
387        PreparedStatement stmt = null;
388        Sql isql = queryable.getDynamicSql();
389        try
390        {
391            if (LOG.isTraceEnabled())
392                LOG.trace("Preparing SQL statement type with [{}] column names", queryable.getParamsNames().length);
393            
394            String query = queryable.query();
395            SQLLOG.info(query);
396            if (columnNames.length > 0)
397                stmt = conn.prepareStatement(query, columnNames);
398            else
399                stmt = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
400            
401            if (isql.getTimeout() > 0)
402                stmt.setQueryTimeout(isql.getTimeout());
403        }
404        catch (SQLException sqle)
405        {
406            throw new RepositoryException("Cannot prepare statement!", sqle);
407        }
408        return stmt;
409    }
410    
411    @Override
412    public <T, R> Command asUpdateCommand(Queryable queryable)
413    {
414        Command command = null;
415        if (queryable.isTypeOfBulk())
416            command = new BulkJdbcCommand(queryable, this.conn)
417                .with(this.newStatement(queryable));
418        else
419            command = new DefaultJdbcCommand(queryable, this.conn)
420                            .with(this.newStatement(queryable));
421        
422        return command;
423    }
424    
425    @Override
426    public <T, R> Command asRemoveCommand(Queryable queryable)
427    {
428        Command command = null;
429        if (queryable.isTypeOfBulk())
430            command = new BulkJdbcCommand(queryable, this.conn)
431                            .with(this.newStatement(queryable));
432        else
433            command = new DefaultJdbcCommand(queryable, this.conn)
434                            .with(this.newStatement(queryable));
435        
436        return command;
437    }
438    
439    @Override
440    public <T, R> Command asAddCommand(Queryable queryable)
441    {
442        Command command = null;
443        Insertable sql = queryable.getDynamicSql().asInsertable();
444        if (sql.isAutoGenerateKey())
445        {
446            if (sql.getAutoGeneratedKey().isAutoStrategy())
447            {
448                StatementAdapter stmt = this.newInsertStatement(queryable);
449                command = new AddAutoKeyJdbcCommand(queryable, this.conn).with(stmt);
450            }
451            else if (sql.getAutoGeneratedKey().isSequenceStrategy())
452            {
453                command = new AddSequenceKeyJdbcCommand(queryable, this.conn)
454                            .with(this.newInsertStatement(queryable));
455            }
456        }
457        else if (queryable.isTypeOfBulk())
458            command = new BulkJdbcCommand(queryable, this.conn)
459                            .with(this.newStatement(queryable));
460        else
461            command = new DefaultJdbcCommand(queryable, this.conn)
462                            .with(this.newStatement(queryable));
463        
464        return command;
465    }
466    
467    @Override
468    public <T, R> Command asSelectCommand(Queryable queryable, ResultRow<T, R> overloadResultRow)
469    {
470        Command command = null;
471        //StatementAdapter<Number, ResultSet> adapterStmtCount = null;
472        //if (queryable.isPaging())
473        //    adapterStmtCount = newStatement(queryable.queryCount());
474        
475        StatementAdapter<T, R> stmt = this.newStatement(queryable);
476        //Selectable select = queryable.getDynamicSql().asSelectable();
477        stmt.with(overloadResultRow);
478            //.oneToManies(select.getOneToMany())
479            //.groupingBy(select.getGroupByAsList())
480        
481        command = new DefaultJdbcQuery(queryable, this.conn).with(stmt);
482        return command;
483    }
484    
485}