All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src-main.org.awakefw.sql.jdbc.StatementHttp Maven / Gradle / Ivy

/*
 * Awake File: Easy file upload & download through HTTP with Java
 * Awake SQL: Remote JDBC access through HTTP.                                    
 * Copyright (C) 2012, Kawan Softwares S.A.S.
 * (http://www.awakeframework.org). All rights reserved.                                
 *                                                                               
 * Awake File/SQL is free software; you can redistribute it and/or                 
 * modify it under the terms of the GNU Lesser General Public                    
 * License as published by the Free Software Foundation; either                  
 * version 2.1 of the License, or (at your option) any later version.            
 *                                                                               
 * Awake File/SQL is distributed in the hope that it will be useful,               
 * but WITHOUT ANY WARRANTY; without even the implied warranty of                
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU             
 * Lesser General Public License for more details.                               
 *                                                                               
 * You should have received a copy of the GNU Lesser General Public              
 * License along with this library; if not, write to the Free Software           
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
 * 02110-1301  USA
 *
 * Any modifications to this file must keep this entire header
 * intact.
 */

// Last Updates:
// 12/07/07 20:00 NDP - creation
// 12/08/07 12:25 NDP - remove a println() in executeQuery()
// 05/11/08 15:20 NDP - ERR_HTTP_* are now Strings
// 12/11/08 16:10 NDP - Set Exception & error code
// 02/12/09 12:00 NDP - Add SESSION_ID parameter
// 03/02/10 18:20 NDP - Pass ConnectionHttp in constructor
// 18/02/10 15:55 NDP - StatementHttp Fix bug: action was for prepared statement instead 
//                      of statement in getFileFromexecuteOnServer
// 30/09/10 18:50 NDP - Version that throws AwakeSQLException
// 05/10/10 17:50 NDP - StatementHttp: fix bug - getFileFromexecuteOnServer(): token was not passed to server
// 05/10/10 17:55 NDP - StatementHttp: fix bug - getFileFromexecuteOnServer(): string was not passed to server
// 05/10/10 17:55 NDP - StatementHttp:
// 10/11/10 15:30 NDP - StatementHttp: include HttpTransfer.recv() when throwing Awake Exception
// 06/03/11 21:20 NDP - StatementHttp: use StatementHolder
// 09/03/11 20:05 NDP - StatementHttp: Encrypt the sql order for obsfucation need
// 11/03/11 14:50 NDP - StatementHttp: executeQuery() executes only one StatementHolder
// 15/03/11 15:55 NDP - StatementHttp: call connectionHttp.resetStatementHolderList() in finally in case of exception
// 27/09/11 19:25 NDP : StatementHttp: add get/setMaxRows & get/setFetchSize
// 28/09/11 19:25 NDP : StatementHttp: add get/setQueryTimeout
// 29/09/11 20:05 NDP : StatementHttp: add setEscapeProcessing
// 16/10/11 21:05 NDP : StatementHttp: executeQuery() clean the IllegalStateException message
// 16/10/11 21:05 NDP : StatementHttp: implement addBatch() / clearBatch() / executeBatch() / getConnection()
// 29/10/11 12:05 NDP : StatementHttp: return empty int[] from executeBatch() if batch stack empty
// 29/10/11 13:30 NDP : StatementHttp: all http execute now in JdbcHttpBatchTransfer
// 10/11/11 19:00 NDP : StatementHttp: escape processing is int (for -1 not set value)
// 16/11/11 19:30 NDP : StatementHttp: implement execute() / getResultSet() / getUpdateCount()
// 16/11/11 20:40 NDP : StatementHttp: add get/clearWanings()
// 18/11/11 20:00 NDP : StatementHttp: implements Statement
// 13/03/12 13:45 NDP : StatementHttp: uses now StatementHolderFileList to store statements holders for batchs
// 13/03/12 16:15 NDP : StatementHttp: free memory with set null in close();
// 06/04/12 20:25 NDP : StatementHttp: manage StatementHolder.setExecuteUpdate

package org.awakefw.sql.jdbc;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.awakefw.commons.jdbc.abstracts.AbstractStatement;
import org.awakefw.file.util.AwakeLogger;
import org.awakefw.file.util.KeepTempFilePolicyParms;
import org.awakefw.file.util.Tag;
import org.awakefw.sql.jdbc.http.JdbcHttpBatchTransfer;
import org.awakefw.sql.jdbc.http.JdbcHttpExecuteRawTransfer;
import org.awakefw.sql.jdbc.http.JdbcHttpStatementTransfer;
import org.awakefw.sql.jdbc.http.JdbcHttpTransferUtil;
import org.awakefw.sql.jdbc.util.StatementHolderFileList;
import org.awakefw.sql.json.BatchResultsTransport;
import org.awakefw.sql.json.StatementHolder;

/**
 * Creates and handle a Statement Cache. 
* It works exactly as a "normal" Statement, try to get the ResultSet in Java * memory when they are cache, instead of executing them in the JDBC/SQL space * area. * */ public class StatementHttp extends AbstractStatement implements Statement { /** Debug flag */ public boolean DEBUG = false; /** The HttpConnection in use */ protected ConnectionHttp connectionHttp = null; /** The result set type. Defaults to ResultSet.TYPE_FORWARD_ONLY */ protected int resultSetType = ResultSet.TYPE_FORWARD_ONLY; /** The result set concurrency. Defaults to CONCUR_READ_ONLY */ protected int resultSetConcurrency = ResultSet.CONCUR_READ_ONLY; /** The result set holdability. Defaults to CLOSE_CURSORS_AT_COMMIT */ protected int resultSetHoldability = ResultSet.CLOSE_CURSORS_AT_COMMIT; /** Max rows */ protected int maxRows = 0; /** Fetch Size */ protected int fetchSize = 0; /** Query Timeout */ protected int queryTimeout = 0; /** Escape processing. We use a int value to store the fact it is not set */ protected int escapeProcessingInt = -1; // true is default value /** The ResultSet returned by the remote execute() */ protected ResultSet rsFromExecute = null; /** The updateCount returned by the remote execute() */ protected int updateCount = -1; /** The list of statement batch to be executed */ //protected List batchHolderList = new Vector(); /** The list of statement holder are stored on a file */ protected StatementHolderFileList batchHolderFileList = null; /** * Constructor * * @param connectionHttp * The http Connection * @param resultSetType * The result set type * @param resultSetConcurrency * The result set concurrency * @param resultSetHoldability * The result set holdability */ public StatementHttp(ConnectionHttp connectionHttp, int resultSetType, int resultSetConcurrency, int resultSetHoldability) { this.connectionHttp = connectionHttp; this.resultSetType = resultSetType; this.resultSetConcurrency = resultSetConcurrency; this.resultSetHoldability = resultSetHoldability; this.batchHolderFileList = new StatementHolderFileList(connectionHttp); } /** * Test if a prepared statement is still open * * @throws SQLException * it the Connection is closed */ protected void testIfClosed() throws SQLException { if (isClosed()) { throw new SQLException("This Awake Statement is closed!"); } } /** * Begin Kawan Softwares S.A.S implementation if Result Set is cached, * return the cached version; else create the Result Set with a normal * executeQuery() and put the result in memory End Kawan Softwares S.A.S * implementation * * Executes the given SQL statement, which returns a single * ResultSet object. * * @param sql * an SQL statement to be sent to the database, typically a * static SQL SELECT statement * @return a ResultSet object that contains the data produced * by the given query; never null * @exception SQLException * if a database access error occurs or the given SQL * statement produces anything other than a single * ResultSet object */ @Override public ResultSet executeQuery(String sql) throws SQLException { testIfClosed(); if (sql == null) { throw new SQLException("sql order is null!"); } sql = sql.trim(); if (!connectionHttp.getAutoCommit()) { throw new IllegalStateException( Tag.AWAKE + "executeQuery() can\'t be executed when auto commit is off"); } StatementHolder statementHolder = new StatementHolder(sql, resultSetType, resultSetConcurrency, resultSetHoldability); statementHolder.setFetchSize(fetchSize); statementHolder.setMaxRows(maxRows); statementHolder.setQueryTimeout(queryTimeout); statementHolder.setEscapeProcessing(escapeProcessingInt); statementHolder.setPreparedStatement(false); statementHolder.setExecuteUpdate(false); // Send order to Server to SQL Executor JdbcHttpStatementTransfer jdbcHttpStatementTransfer = new JdbcHttpStatementTransfer( connectionHttp, connectionHttp.getAuthenticationToken()); File receiveFile = jdbcHttpStatementTransfer .getFileFromExecuteQueryOnServer(statementHolder); debug("getFileFromexecuteOnServer() : " + receiveFile); // Transform the Result Set in String back to an Result Set (emulated) ResultSet rs = new ResultSetHttp(connectionHttp, statementHolder, this, receiveFile); return rs; } /** * Executes the given SQL statement, which may be an INSERT, * UPDATE, or DELETE statement or an SQL statement * that returns nothing, such as an SQL DDL statement. * * @param sql * an SQL INSERT, UPDATE or * DELETE statement or an SQL statement that returns * nothing * @return either the row count for INSERT, UPDATE * or DELETE statements, or 0 for SQL * statements that return nothing * @exception SQLException * if a database access error occurs or the given SQL * statement produces a ResultSet object */ @Override public int executeUpdate(String sql) throws SQLException { testIfClosed(); if (sql == null) { throw new SQLException("sql order is null!"); } sql = sql.trim(); int rc = 0; // Add the statement to the statement list StatementHolder statementHolder = new StatementHolder(sql, resultSetType, resultSetConcurrency, resultSetHoldability); statementHolder.setPreparedStatement(false); statementHolder.setExecuteUpdate(true); connectionHttp.addStatementHolder(statementHolder); // Execute if (connectionHttp.getAutoCommit()) { try { // Send order to Server to SQL Executor String rcStr = connectionHttp .getStringFromExecuteUpdateListOnServer(); try { rc = Integer.parseInt(rcStr); } catch (NumberFormatException e) { throw new SQLException( Tag.AWAKE_PRODUCT_FAIL + e.getMessage(), new IOException( e.getMessage(), e)); } } finally { connectionHttp.resetStatementHolderList(); // Safety reset of // list } } return rc; } // ----------------------- Multiple Results -------------------------- /** * Executes the given SQL statement, which may return multiple results. In * some (uncommon) situations, a single SQL statement may return multiple * result sets and/or update counts. Normally you can ignore this unless you * are (1) executing a stored procedure that you know may return multiple * results or (2) you are dynamically executing an unknown SQL string. *

* The execute method executes an SQL statement and indicates * the form of the first result. You must then use the methods * getResultSet or getUpdateCount to retrieve the * result, and getMoreResults to move to any subsequent * result(s). * * @param sql * any SQL statement * @return true if the first result is a ResultSet * object; false if it is an update count or there are * no results * @exception SQLException * if a database access error occurs * @see #getResultSet * @see #getUpdateCount * @see #getMoreResults */ public boolean execute(String sql) throws SQLException { testIfClosed(); if (sql == null) { throw new SQLException("sql order is null!"); } sql = sql.trim(); // if (!connectionHttp.getAutoCommit()) { // throw new IllegalStateException( // Tag.AWAKE // + "executeQuery() can\'t be executed when auto commit is off"); // } StatementHolder statementHolder = new StatementHolder(sql, resultSetType, resultSetConcurrency, resultSetHoldability); statementHolder.setPreparedStatement(false); statementHolder.setExecuteUpdate(false); statementHolder.setFetchSize(fetchSize); statementHolder.setMaxRows(maxRows); statementHolder.setQueryTimeout(queryTimeout); statementHolder.setEscapeProcessing(escapeProcessingInt); // Reset the fields values rsFromExecute = null; updateCount = -1; // Send order to Server to SQL Executor JdbcHttpExecuteRawTransfer jdbcHttpExecuteRawTransfer = new JdbcHttpExecuteRawTransfer( connectionHttp, connectionHttp.getAuthenticationToken()); File receiveFile = jdbcHttpExecuteRawTransfer .getFileFromExecuteRaw(statementHolder); debug("getFileFromexecuteOnServer() : " + receiveFile); boolean fileResultSet = isFileResultSet(receiveFile); if (fileResultSet) { // Transform the Result Set in String back to an Result Set // (emulated) rsFromExecute = new ResultSetHttp(connectionHttp, statementHolder, this, receiveFile); return true; } else { if (!KeepTempFilePolicyParms.KEEP_TEMP_FILE && !DEBUG) { receiveFile.delete(); } return false; } } /** * Says if the file contains a ResultSet or if the file contains an * UpdateCount. if file contains an UpdateCount ==> first line is numeric * * @param file * the received file from the server * @return true if the file is a result set */ protected boolean isFileResultSet(File file) throws SQLException { LineNumberReader lineNumberReader = null; try { lineNumberReader = new LineNumberReader(new FileReader(file)); String line = lineNumberReader.readLine(); line = line.trim(); if (line.startsWith("getUpdateCount=")) { String updateCountStr = StringUtils.substringAfter(line, "getUpdateCount="); try { updateCount = Integer.parseInt(updateCountStr); } catch (NumberFormatException e) { throw new SQLException( Tag.AWAKE_PRODUCT_FAIL + e.getMessage(), new IOException( e.getMessage(), e)); } return false; } else { return true; } } catch (IOException e) { JdbcHttpTransferUtil.wrapExceptionAsSQLException(e); } finally { IOUtils.closeQuietly(lineNumberReader); } return false; } /** * Retrieves the current result as a ResultSet object. This * method should be called only once per result. * * @return the current result as a ResultSet object or * null if the result is an update count or there are * no more results * @exception SQLException * if a database access error occurs * @see #execute */ public ResultSet getResultSet() throws SQLException { return rsFromExecute; } /** * Retrieves the current result as an update count; if the result is a * ResultSet object or there are no more results, -1 is * returned. This method should be called only once per result. * * @return the current result as an update count; -1 if the current result * is a ResultSet object or there are no more results * @exception SQLException * if a database access error occurs * @see #execute */ public int getUpdateCount() throws SQLException { return updateCount; } /** * Moves to this Statement object's next result, returns * true if it is a ResultSet object, and * implicitly closes any current ResultSet object(s) obtained * with the method getResultSet. * *

* There are no more results when the following is true: * *

     *      (!getMoreResults() && (getUpdateCount() == -1)
     * 
* * @return true if the next result is a ResultSet * object; false if it is an update count or there are * no more results * @exception SQLException * if a database access error occurs * @see #execute */ public boolean getMoreResults() throws SQLException { // always return false for now: return false; } /** * Retrieves the first warning reported by calls on this * Statement object. Subsequent Statement object * warnings will be chained to this SQLWarning object. * *

* The warning chain is automatically cleared each time a statement is * (re)executed. This method may not be called on a closed * Statement object; doing so will cause an * SQLException to be thrown. * *

* Note: If you are processing a ResultSet object, any * warnings associated with reads on that ResultSet object will * be chained on it rather than on the Statement object that * produced it. * * @return the first SQLWarning object or null if * there are no warnings * @exception SQLException * if a database access error occurs or this method is called * on a closed statement */ @Override public SQLWarning getWarnings() throws SQLException { return null; } /** * Clears all the warnings reported on this Statement object. * After a call to this method, the method getWarnings will * return null until a new warning is reported for this * Statement object. * * @exception SQLException * if a database access error occurs */ @Override public void clearWarnings() throws SQLException { // Does nothing } /** * Retrieves the maximum number of rows that a ResultSet object * produced by this Statement object can contain. If this limit * is exceeded, the excess rows are silently dropped. * * @return the current maximum number of rows for a ResultSet * object produced by this Statement object; zero means * there is no limit * @exception SQLException * if a database access error occurs * @see #setMaxRows */ @Override public int getMaxRows() throws SQLException { return maxRows; } /** * Sets the limit for the maximum number of rows that any * ResultSet object can contain to the given number. If the * limit is exceeded, the excess rows are silently dropped. * * @param max * the new max rows limit; zero means there is no limit * @exception SQLException * if a database access error occurs or the condition max >= * 0 is not satisfied * @see #getMaxRows */ @Override public void setMaxRows(int max) throws SQLException { maxRows = max; } /** * Gives the JDBC driver a hint as to the number of rows that should be * fetched from the database when more rows are needed. The number of rows * specified affects only result sets created using this statement. If the * value specified is zero, then the hint is ignored. The default value is * zero. * * @param rows * the number of rows to fetch * @exception SQLException * if a database access error occurs, or the condition 0 <= * rows <= this.getMaxRows() is not * satisfied. * @since 1.2 * @see #getFetchSize */ @Override public void setFetchSize(int rows) throws SQLException { fetchSize = rows; } /** * Retrieves the number of result set rows that is the default fetch size * for ResultSet objects generated from this * Statement object. If this Statement object has * not set a fetch size by calling the method setFetchSize, the * return value is implementation-specific. * * @return the default fetch size for result sets generated from this * Statement object * @exception SQLException * if a database access error occurs * @since 1.2 * @see #setFetchSize */ @Override public int getFetchSize() throws SQLException { return fetchSize; } /** * Sets escape processing on or off. If escape scanning is on (the default), * the driver will do escape substitution before sending the SQL statement * to the database. * * Note: Since prepared statements have usually been parsed prior to making * this call, disabling escape processing for * PreparedStatements objects will have no effect. * * @param enable * true to enable escape processing; * false to disable it * @exception SQLException * if a database access error occurs */ @Override public void setEscapeProcessing(boolean enable) throws SQLException { this.escapeProcessingInt = enable ? 1 : 0; } /** * Retrieves the number of seconds the driver will wait for a * Statement object to execute. If the limit is exceeded, a * SQLException is thrown. * * @return the current query timeout limit in seconds; zero means there is * no limit * @exception SQLException * if a database access error occurs * @see #setQueryTimeout */ public int getQueryTimeout() throws SQLException { return this.queryTimeout; } /** * Sets the number of seconds the driver will wait for a * Statement object to execute to the given number of seconds. * If the limit is exceeded, an SQLException is thrown. * * @param seconds * the new query timeout limit in seconds; zero means there is no * limit * @exception SQLException * if a database access error occurs or the condition seconds * >= 0 is not satisfied * @see #getQueryTimeout */ public void setQueryTimeout(int seconds) throws SQLException { this.queryTimeout = seconds; } /** * Retrieves the result set concurrency for ResultSet objects * generated by this Statement object. * * @return either ResultSet.CONCUR_READ_ONLY or * ResultSet.CONCUR_UPDATABLE * @exception SQLException * if a database access error occurs * @since 1.2 */ @Override public int getResultSetConcurrency() throws SQLException { return this.resultSetConcurrency; } /** * Retrieves the result set type for ResultSet objects * generated by this Statement object. * * @return one of ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @exception SQLException * if a database access error occurs * @since 1.2 */ @Override public int getResultSetType() throws SQLException { return this.resultSetType; } /** * Retrieves the result set holdability for ResultSet objects * generated by this Statement object. * * @return either ResultSet.HOLD_CURSORS_OVER_COMMIT or * ResultSet.CLOSE_CURSORS_AT_COMMIT * @exception SQLException * if a database access error occurs * * @since 1.4 */ @Override public int getResultSetHoldability() throws SQLException { return this.resultSetHoldability; } /** * Adds the given SQL command to the current list of commmands for this * Statement object. The commands in this list can be executed * as a batch by calling the method executeBatch. *

* NOTE: This method is optional. * * @param sql * typically this is a static SQL INSERT or * UPDATE statement * @exception SQLException * if a database access error occurs, or the driver does not * support batch updates * @see #executeBatch * @since 1.2 */ @Override public void addBatch(String sql) throws SQLException { testIfClosed(); if (sql == null) { throw new SQLException("sql order is null!"); } sql = sql.trim(); if (connectionHttp.getAutoCommit()) { throw new IllegalStateException(Tag.AWAKE + "addBatch() can\'t be called when auto commit is on."); } connectionHttp.resetStatementHolderList(); // Add the statement to the statement list StatementHolder statementHolder = new StatementHolder(sql, resultSetType, resultSetConcurrency, resultSetHoldability); statementHolder.setPreparedStatement(false); statementHolder.setExecuteUpdate(true); batchHolderFileList.add(statementHolder); } /** * Empties this Statement object's current list of SQL * commands. *

* NOTE: This method is optional. * * @exception SQLException * if a database access error occurs or the driver does not * support batch updates * @see #addBatch * @since 1.2 */ @Override public void clearBatch() throws SQLException { //batchHolderList = new Vector(); this.batchHolderFileList = new StatementHolderFileList(connectionHttp); } /** * Submits a batch of commands to the database for execution and if all * commands execute successfully, returns an array of update counts. The * int elements of the array that is returned are ordered to * correspond to the commands in the batch, which are ordered according to * the order in which they were added to the batch. The elements in the * array returned by the method executeBatch may be one of the * following: *

    *
  1. A number greater than or equal to zero -- indicates that the command * was processed successfully and is an update count giving the number of * rows in the database that were affected by the command's execution *
  2. A value of SUCCESS_NO_INFO -- indicates that the command * was processed successfully but that the number of rows affected is * unknown *

    * If one of the commands in a batch update fails to execute properly, this * method throws a BatchUpdateException, and a JDBC driver may * or may not continue to process the remaining commands in the batch. * However, the driver's behavior must be consistent with a particular DBMS, * either always continuing to process commands or never continuing to * process commands. If the driver continues processing after a failure, the * array returned by the method * BatchUpdateException.getUpdateCounts will contain as many * elements as there are commands in the batch, and at least one of the * elements will be the following: *

    *

  3. A value of EXECUTE_FAILED -- indicates that the command * failed to execute successfully and occurs only if a driver continues to * process commands after a command fails *
*

* A driver is not required to implement this method. The possible * implementations and return values have been modified in the Java 2 SDK, * Standard Edition, version 1.3 to accommodate the option of continuing to * proccess commands in a batch update after a * BatchUpdateException obejct has been thrown. * * @return an array of update counts containing one element for each command * in the batch. The elements of the array are ordered according to * the order in which commands were added to the batch. * @exception SQLException * if a database access error occurs or the driver does not * support batch statements. Throws * {@link BatchUpdateException} (a subclass of * SQLException) if one of the commands sent to * the database fails to execute properly or attempts to * return a result set. * @since 1.3 */ @Override public int[] executeBatch() throws SQLException { int updateCounts[] = new int[batchHolderFileList.size()]; if (batchHolderFileList.size() == 0) { return updateCounts; } JdbcHttpBatchTransfer jdbcHttpBatchTransfer = new JdbcHttpBatchTransfer( connectionHttp, connectionHttp.getAuthenticationToken()); String updateCountsStr = jdbcHttpBatchTransfer .getStringFromExecuteStatementBatchOnServer(batchHolderFileList); updateCounts = BatchResultsTransport.fromJson(updateCountsStr); clearBatch(); return updateCounts; } /** * Retrieves the Connection object that produced this * Statement object. * * @return the connection that produced this statement * @exception SQLException * if a database access error occurs * @since 1.2 */ @Override public Connection getConnection() throws SQLException { return this.connectionHttp; } /** * Releases this Statement object's database and JDBC resources * immediately instead of waiting for this to happen when it is * automatically closed. It is generally good practice to release resources * as soon as you are finished with them to avoid tying up database * resources. *

* Calling the method close on a Statement object * that is already closed has no effect. *

* Note: A Statement object is automatically closed when * it is garbage collected. When a Statement object is closed, * its current ResultSet object, if one exists, is also closed. * * @exception SQLException * if a database access error occurs */ public void close() throws SQLException { super.close(); batchHolderFileList = null; } /** * Displays the given message if DEBUG is set. * * @param sMsg * the debug message */ private void debug(String s) { if (DEBUG) { AwakeLogger.log(s); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy