AbstractSqlCommand.java
上传用户:zhengdagz
上传日期:2014-03-06
资源大小:1956k
文件大小:12k
源码类别:

xml/soap/webservice

开发平台:

Java

  1. /*
  2.  * $Id: AbstractSqlCommand.java,v 1.6 2005/10/10 17:01:15 rbair Exp $
  3.  *
  4.  * Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle,
  5.  * Santa Clara, California 95054, U.S.A. All rights reserved.
  6.  *
  7.  * This library is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU Lesser General Public
  9.  * License as published by the Free Software Foundation; either
  10.  * version 2.1 of the License, or (at your option) any later version.
  11.  * 
  12.  * This library is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  * Lesser General Public License for more details.
  16.  * 
  17.  * You should have received a copy of the GNU Lesser General Public
  18.  * License along with this library; if not, write to the Free Software
  19.  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  20.  */
  21. package org.jdesktop.dataset.provider.sql;
  22. import java.sql.PreparedStatement;
  23. import java.util.ArrayList;
  24. import java.util.HashMap;
  25. import java.util.HashSet;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Set;
  29. import org.jdesktop.dataset.DataCommand;
  30. import org.jdesktop.dataset.DataRow;
  31. /**
  32.  * <p>An AbstractSqlCommand is a {@link org.jdesktop.dataset.DataCommand} meant to 
  33.  * be used with a {@link SQLDataProvider} by defining 
  34.  * methods to generate a SELECT, INSERT, UPDATE or DELETE statement. How these
  35.  * SQL statements are built is up to the concrete implementation of an
  36.  * AbstractSqlCommand.
  37.  *
  38.  * <p>The methods for retrieving the SQL statements (as PreparedStatements) are
  39.  * all protected, meaning they can be accessed by subclasses as well as classes
  40.  * within this package. Thus, within the package, the AbstractSqlCommand defines
  41.  * an interface for preparing a PreparedStatement for consumers of the command.
  42.  *
  43.  * <p>An AbstractSqlCommand also defines methods to normalize SQL statements to 
  44.  * make them easier to process using JDBC--namely, to convert between named-
  45.  * parameter style statements and index-based parameterized statements. 
  46.  *
  47.  * <p>AbstractSqlCommand is useful in defining the structure of a concrete SQLCommand
  48.  * or TableCommand, and is not meant to be used on its own.
  49.  *
  50.  * @author rbair
  51.  */
  52. public abstract class AbstractSqlCommand extends DataCommand {
  53.     
  54.     /**
  55.      * @param conn An active JDBCDataConnection to use to prepare the statement
  56.      * @return A PreparedStatement, ready to execute, for the SELECT SQL statement.
  57.      */
  58.     protected abstract PreparedStatement getSelectStatement(JDBCDataConnection conn) throws Exception;
  59.     /**
  60.      * @param conn An active JDBCDataConnection to use to prepare the statement
  61.      * @param row The {@link DataRow} that will be inserted
  62.      * @return A PreparedStatement, ready to execute, for the INSERT SQL statement.
  63.      */
  64.     protected abstract PreparedStatement getInsertStatement(JDBCDataConnection conn, DataRow row) throws Exception;
  65.     /**
  66.      * @param conn An active JDBCDataConnection to use to prepare the statement
  67.      * @param row The {@link DataRow} that will be updated
  68.      * @return A PreparedStatement, ready to execute, for the UPDATE SQL statement.
  69.      */
  70.     protected abstract PreparedStatement getUpdateStatement(JDBCDataConnection conn, DataRow row) throws Exception;
  71.     /**
  72.      * @param conn An active JDBCDataConnection to use to prepare the statement
  73.      * @param row The {@link DataRow} that will be deleted
  74.      * @return A PreparedStatement, ready to execute, for the DELETE SQL statement.
  75.      */
  76.     protected abstract PreparedStatement getDeleteStatement(JDBCDataConnection conn, DataRow row) throws Exception;
  77.     
  78.     /** 
  79.      * Generates a new String for a SQL statement, replacing named parameters 
  80.      * with ? symbols, as required by the 
  81.      * {@link java.sql.Connection#prepareStatement(String)} method. The Map
  82.      * parameter is populated with parameter names, mapped to a List
  83.      * of indexes numbering that parameter within the SQL statement. Thus, as each
  84.      * named parameter is replaced, we get a list of the position that parameter 
  85.      * had within the statement; the List of indexes can then be used on a call to 
  86.      * {@link PreparedStatement#setObject(int, Object)}, using the index in the list
  87.      * as the index parameter in setObject().
  88.      *
  89.      * @param sql A SQL statement with 1 or more named parameters.
  90.      * @param indexes An empty Map which will be populated with a list of parameter
  91.      * names, and the number of that parameter within the SQL statement.
  92.      * @return A SQL statement ready to use in the JDBC prepareStatement() method; note
  93.      * that the Map parameter is also populated in this method call.
  94.      */
  95.     protected String constructSql(String sql, Map<String,List<Integer>> indexes) {
  96.         //replace all of the named parameters in the sql with their
  97.         //corrosponding values. This is done by first converting the sql
  98.         //to proper JDBC sql by inserting '?' for each and every param.
  99.         //As this is done, a record is kept of which parameters go with
  100.         //which indexes. Then, the parameter values are applied.
  101.         StringBuilder buffer = new StringBuilder(sql);
  102.         //variable containing the index of the current parameter. So,
  103.         //for the first named param this is 0, then 1 for the next, and so on
  104.         int paramIndex = 0;
  105.         //iterate through the buffer looking for a colon outside of any
  106.         //single or double quotes. This represents the beginning of a named
  107.         //parameter
  108.         boolean inSingleQuote = false;
  109.         boolean inDoubleQuote = false;
  110.         for (int i=0; i<buffer.length(); i++) {
  111.             char c = buffer.charAt(i);
  112.             if (c == ''') {
  113.                 inSingleQuote = !inSingleQuote;
  114.             } else if (c == '"') {
  115.                 inDoubleQuote = !inDoubleQuote;
  116.             } else if (c == ':' && !inSingleQuote && !inDoubleQuote) {
  117.                 //found the beginning of a named param. find the whole
  118.                 //name by looking from here to the first whitespace
  119.                 //character
  120.                 int firstCharIndex = i;
  121.                 i++;
  122.                 boolean found = false;
  123.                 while (!found) {
  124.                     if (i >= buffer.length()) {
  125.                         //I've gotten to the end of the string, so I must
  126.                         //now have the entire variable name
  127.                         found = true;
  128.                     } else {
  129.                         char next = buffer.charAt(i);
  130.                         if (next == ' ' || next == 'n' || next == 't' || next == 'r' || next == ',' || next == ')') {
  131.                             found = true;
  132.                         }
  133.                     }
  134.                     i++;
  135.                 }
  136.                 //ok, i-1 is the index following the last character in this sequence.
  137.                 String paramName = buffer.substring(firstCharIndex+1, i-1);
  138.                 //now that I have the name, replace it with a ? and add it
  139.                 //to the map of paramName->index values.
  140.                 buffer.replace(firstCharIndex, i-1, "?");
  141.                 if (!indexes.containsKey(paramName)) {
  142.                     indexes.put(paramName, new ArrayList<Integer>());
  143.                 }
  144.                 List<Integer> list = indexes.get(paramName);
  145.                 list.add(paramIndex++);
  146.                 //reposition "i" to a valid value since a lot of chars were
  147.                 //just removed
  148.                 i = firstCharIndex + 1;
  149.             }
  150.         }
  151.         return buffer.toString();
  152.     }
  153.     /**
  154.      * Creates a PreparedStatement from a SQL statement, setting parameter
  155.      * values using the supplied Map of parameter names to values. If there are
  156.      * parameters in the SQL that require values assigned in the PreparedStatements,
  157.      * the parameter in the SQL should appear as ":<parameter-name>", and the
  158.      * parameter name should be assigned a value in the Map argument to this
  159.      * method.
  160.      *
  161.      * @param sql A SQL statement, including named parameters if desired.
  162.      * @param values A Map of parameter names to Object values for the parameter--
  163.      * the values used for each parameter when this statement is executed.
  164.      * @param conn An valid JDBCDataConnection.
  165.      * @return A PreparedStatement build from the SQL argument, with parameters
  166.      * assigned.
  167.      * @throws Exception if any error occurs during execution.
  168.      */
  169.     protected PreparedStatement prepareStatement(String sql, Map<String,Object> values, JDBCDataConnection conn) throws Exception {
  170.         //map containing the indexes for each named param
  171.         Map<String,List<Integer>> indexes = new HashMap<String,List<Integer>>();
  172.         PreparedStatement ps = conn.prepareStatement(constructSql(sql, indexes));
  173.         //now, apply the given set of parameters
  174.         for (String paramName : getParameterNames(new String[]{sql})) {
  175.             List<Integer> list = indexes.get(paramName);
  176.             if (list != null) {
  177.                 for (int index : list) {
  178.                     ps.setObject(index + 1, values.get(paramName));
  179.                 }
  180.             }
  181.         }
  182.         // TODO: should check that we have values for all parameters in the SQL, and that no parameters in the Map are unused (PWW 04/25/05)
  183.         return ps;
  184.     }
  185.     /**
  186.      * Searches the statements for param names, and returns the unique set of
  187.  * param names.
  188.      *
  189.      * @param statements An array of SQL statements, optionally with named 
  190.      * parameters embedded, in the form ":<parameter-name>".
  191.      * @return Array of parameter names, unique across all the statements.
  192.     */
  193. public String[] getParameterNames(String[] statements) {
  194. StringBuilder buffer = new StringBuilder();
  195. for (String s : statements) {
  196. buffer.append(s);
  197.     buffer.append("n");
  198. }
  199. Set<String> names = new HashSet<String>();
  200.         // TODO: this search routine is redundant with constructSQL above (PWW 04/25/05)
  201.         
  202.         //iterate through the buffer looking for a colon outside of any
  203.         //single or double quotes. This represents the beginning of a named
  204.         //parameter
  205.         boolean inSingleQuote = false;
  206.         boolean inDoubleQuote = false;
  207.         for (int i=0; i<buffer.length(); i++) {
  208.             char c = buffer.charAt(i);
  209.             if (c == ''') {
  210.                 inSingleQuote = !inSingleQuote;
  211.             } else if (c == '"') {
  212.                 inDoubleQuote = !inDoubleQuote;
  213.             } else if (c == ':' && !inSingleQuote && !inDoubleQuote) {
  214.                 //found the beginning of a named param. find the whole
  215.                 //name by looking from here to the first whitespace
  216.                 //character
  217.                 int firstCharIndex = i;
  218.                 i++;
  219.                 boolean found = false;
  220.                 while (!found) {
  221.                     if (i >= buffer.length()) {
  222.                         //I've gotten to the end of the string, so I must
  223.                         //now have the entire variable name
  224.                         found = true;
  225.                     } else {
  226.                         char next = buffer.charAt(i);
  227.                         if (next == ' ' || next == 'n' || next == 't' || next == 'r' || next == ',' || next == ')') {
  228.                             found = true;
  229.                         }
  230.                     }
  231.                     i++;
  232.                 }
  233.                 //ok, i-1 is the index following the last character in this sequence.
  234.                 String paramName = buffer.substring(firstCharIndex+1, i-1);
  235.                 names.add(paramName);
  236.             }
  237.         }
  238.         String[] results = new String[names.size()];
  239.         return names.toArray(results);
  240. }
  241. }