From 69c8f4136daff56615accf31a8c9837fe70ebbf8 Mon Sep 17 00:00:00 2001
From: Martin Goik <goik@hdm-stuttgart.de>
Date: Wed, 22 Oct 2014 00:49:28 +0200
Subject: [PATCH] JDBC parameter type safe solution

---
 Sda2/P/UnixSqlLdap/Jndi/usermanage/.gitignore |   2 +
 .../sda2/usermanage/rdbms/DatabaseObject.java |   5 +
 .../mi/sda2/usermanage/rdbms/GroupParam.java  |  26 ++
 .../sda2/usermanage/rdbms/MyNamedQuery.java   |  45 +++
 .../rdbms/NamedParameterStatement.java        | 342 ++++++++++++++++++
 .../mi/sda2/usermanage/rdbms/Param.java       |   6 +
 .../mi/sda2/usermanage/rdbms/UserParam.java   |  30 ++
 7 files changed, 456 insertions(+)
 create mode 100644 Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/DatabaseObject.java
 create mode 100644 Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/GroupParam.java
 create mode 100644 Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/MyNamedQuery.java
 create mode 100644 Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/NamedParameterStatement.java
 create mode 100644 Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/Param.java
 create mode 100644 Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/UserParam.java

diff --git a/Sda2/P/UnixSqlLdap/Jndi/usermanage/.gitignore b/Sda2/P/UnixSqlLdap/Jndi/usermanage/.gitignore
index 731eb433c..a1c3ab4d0 100644
--- a/Sda2/P/UnixSqlLdap/Jndi/usermanage/.gitignore
+++ b/Sda2/P/UnixSqlLdap/Jndi/usermanage/.gitignore
@@ -1,2 +1,4 @@
 /target/
 /.settings/
+.classpath
+.project
diff --git a/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/DatabaseObject.java b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/DatabaseObject.java
new file mode 100644
index 000000000..67d1d7ee9
--- /dev/null
+++ b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/DatabaseObject.java
@@ -0,0 +1,5 @@
+package de.hdm_stuttgart.mi.sda2.usermanage.rdbms;
+
+public interface DatabaseObject {
+	public void setDatabaseId(int databaseId);
+}
diff --git a/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/GroupParam.java b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/GroupParam.java
new file mode 100644
index 000000000..51eda5b08
--- /dev/null
+++ b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/GroupParam.java
@@ -0,0 +1,26 @@
+package de.hdm_stuttgart.mi.sda2.usermanage.rdbms;
+
+public enum GroupParam implements Param {
+
+	// See index values in subsequent SQL statement
+	gid(1),
+	gidNumber(2);
+	
+	public static final String sql="INSERT INTO SysGroup (gid, gidNumber) "
+		                                 /* Parameter index  1          2 */
+    			                                   + "VALUES(?,         ?)";
+
+	public final int index;
+	
+	GroupParam(final int index){
+		this.index = index;
+	}
+	public int getIndex() {
+		return index;
+	}
+	
+	public String getSql() {
+		return sql;
+	}
+	
+}
diff --git a/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/MyNamedQuery.java b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/MyNamedQuery.java
new file mode 100644
index 000000000..2dc1dc9f0
--- /dev/null
+++ b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/MyNamedQuery.java
@@ -0,0 +1,45 @@
+package de.hdm_stuttgart.mi.sda2.usermanage.rdbms;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class MyNamedQuery <T extends Param>{
+	
+	final PreparedStatement statement;
+	
+	/**
+	 * See {@link Connection#prepareStatement(String, int)}.
+	 * @param conn
+	 * @param t An arbitrary enum param value belonging to the SQL statement in question
+	 * @param autoGeneratedKeys
+	 * @throws SQLException
+	 */
+	public MyNamedQuery(final Connection conn, final T t, final int autoGeneratedKeys) 
+	  throws SQLException {
+		statement = conn.prepareStatement(t.getSql(), autoGeneratedKeys);
+	}
+	
+	public void setString(T t, String value) throws SQLException {
+		statement.setString(t.getIndex(), value);
+	}
+	
+	public void setInt(T t, int value) throws SQLException {
+		statement.setInt(t.getIndex(), value);
+	}
+
+	public PreparedStatement getStatement() {
+		return statement;
+	}
+    /**
+     * Executes all of the batched statements.
+     * 
+     * See {@link Statement#executeBatch()} for details.
+     * @return update counts for each statement
+     * @throws SQLException if something went wrong
+     */
+    public int[] executeBatch() throws SQLException {
+        return statement.executeBatch();
+    }
+}
diff --git a/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/NamedParameterStatement.java b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/NamedParameterStatement.java
new file mode 100644
index 000000000..7f676c287
--- /dev/null
+++ b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/NamedParameterStatement.java
@@ -0,0 +1,342 @@
+package de.hdm_stuttgart.mi.sda2.usermanage.rdbms;
+
+/*
+ * Code based on http://www.javaworld.com/article/2077706/core-java/named-parameters-for-preparedstatement.html
+ * 
+ * This version see
+ * https://code.google.com/p/framework-pegasus/source/browse/Pegasus/trunk/src/ar/pegasus/framework/util/jdbc/NamedParameterStatement.java
+ * 
+ * parse method slightly modified by Goik
+ * 
+ */
+
+
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Timestamp;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author goik
+ *
+ */
+public class NamedParameterStatement {
+    /** The statement this object is wrapping. */
+    private final PreparedStatement statement;
+
+    /** Maps parameter names to arrays of ints which are the parameter indices. */
+    private final Map<String, int[]> indexMap;
+
+
+    /**
+     * Creates a NamedParameterStatement.  Wraps a call to
+     * See {@link Connection#prepareStatement(String, int)}.
+     * @param connection the database connection
+     * @param query      the parameterized query
+     * @throws SQLException if the statement could not be created
+     */
+    public NamedParameterStatement(Connection connection, String query,  int autoGeneratedKeys) throws SQLException {
+        indexMap=new HashMap<String, int[]>();
+        String parsedQuery=parse(query, indexMap);
+        statement=connection.prepareStatement(parsedQuery, autoGeneratedKeys);
+    }
+
+
+    /**
+     * Parses a query with named parameters.  The parameter-index mappings are 
+     * put into the map, and the
+     * parsed query is returned.  DO NOT CALL FROM CLIENT CODE.  This 
+     * method is non-private so JUnit code can
+     * test it.
+     * @param query    query to parse
+     * @param paramMap map to hold parameter-index mappings
+     * @return the parsed query
+     */
+    static final String parse(String query, Map<String, int[]> paramMap) {
+        // I was originally using regular expressions, but they didn't work well 
+        // for ignoring parameter-like strings inside quotes.
+        Map<String, List<Integer>> paramMapAux = new HashMap<String, List<Integer>>();
+        int length=query.length();
+        StringBuffer parsedQuery=new StringBuffer(length);
+        boolean inSingleQuote=false;
+        boolean inDoubleQuote=false;
+        int index=1;
+
+//        for (int i=0;i<length;i++) {
+//        	char c=query.charAt(i);
+//        	
+//        	switch(c) {
+//        	
+//        	case '\'':
+//        		inSingleQuote = !inSingleQuote;
+//        		break;
+//        	
+//        	case '"':
+//        		inDoubleQuote = !inDoubleQuote;
+//        		break;
+//        	}
+//        	if (!inSingleQuote && !inDoubleQuote && c == ':' && i+1<length &&
+//                    Character.isJavaIdentifierStart(query.charAt(i+1))) {
+//        		int j=i+2;
+//                while(j<length && Character.isJavaIdentifierPart(query.charAt(j))) {
+//                    j++;
+//                }
+//                String name=query.substring(i+1,j);
+//                c='?'; // replace the parameter with a question mark
+//                i+=name.length(); // skip past the end if the parameter
+//
+//                List<Integer> indexList= paramMapAux.get(name);
+//                if(indexList==null) {
+//                    indexList=new LinkedList<Integer>();
+//                    paramMapAux.put(name, indexList);
+//                }
+//                indexList.add(index);
+//
+//                index++;
+//        	}
+//        	parsedQuery.append(c);
+//        }
+        
+        for(int i=0;i<length;i++) {
+            char c=query.charAt(i);
+            if(inSingleQuote) {
+                if(c=='\'') {
+                    inSingleQuote=false;
+                }
+            } else if(inDoubleQuote) {
+                if(c=='"') {
+                    inDoubleQuote=false;
+                }
+            } else {
+                if(c=='\'') {
+                    inSingleQuote=true;
+                } else if(c=='"') {
+                    inDoubleQuote=true;
+                } else if(c==':' && i+1<length &&
+                        Character.isJavaIdentifierStart(query.charAt(i+1))) {
+                    int j=i+2;
+                    while(j<length && Character.isJavaIdentifierPart(query.charAt(j))) {
+                        j++;
+                    }
+                    String name=query.substring(i+1,j);
+                    c='?'; // replace the parameter with a question mark
+                    i+=name.length(); // skip past the end if the parameter
+
+                    List<Integer> indexList= paramMapAux.get(name);
+                    if(indexList==null) {
+                        indexList=new LinkedList<Integer>();
+                        paramMapAux.put(name, indexList);
+                    }
+                    indexList.add(index);
+
+                    index++;
+                }
+            }
+            parsedQuery.append(c);
+        }
+
+        // replace the lists of Integer objects with arrays of ints
+        for(Map.Entry<String, List<Integer>> entry : paramMapAux.entrySet()) {
+            List<Integer> list=entry.getValue();
+            int[] indexes=new int[list.size()];
+            int i=0;
+            for(Integer x : list) {
+                indexes[i++]=x;
+            }
+            paramMap.put(entry.getKey(), indexes) ;
+        }
+
+        return parsedQuery.toString();
+    }
+
+    
+    
+
+    /**
+     * Returns the indexes for a parameter.
+     * @param name parameter name
+     * @return parameter indexes
+     * @throws IllegalArgumentException if the parameter does not exist
+     */
+    private int[] getIndexes(String name) {
+        int[] indexes=indexMap.get(name);
+        if(indexes==null) {
+            throw new IllegalArgumentException("Parameter not found: "+name);
+        }
+        return indexes;
+    }
+
+
+    /**
+     * Sets a parameter.
+     * @param name  parameter name
+     * @param value parameter value
+     * @throws SQLException if an error occurred
+     * @throws IllegalArgumentException if the parameter does not exist
+     * @see PreparedStatement#setObject(int, java.lang.Object)
+     */
+    public void setObject(String name, Object value) throws SQLException {
+        int[] indexes=getIndexes(name);
+        for(int i=0; i < indexes.length; i++) {
+            statement.setObject(indexes[i], value);
+        }
+    }
+    
+    /**
+     * See {@link Statement#getGeneratedKeys()}
+     * @return
+     * @throws SQLException
+     */
+    public ResultSet getGeneratedKeys() throws SQLException {
+    	return statement.getGeneratedKeys();
+    }
+
+    /**
+     * Sets a parameter.
+     * @param name  parameter name
+     * @param value parameter value
+     * @throws SQLException if an error occurred
+     * @throws IllegalArgumentException if the parameter does not exist
+     * @see PreparedStatement#setString(int, java.lang.String)
+     */
+    public void setString(String name, String value) throws SQLException {
+        int[] indexes=getIndexes(name);
+        for(int i=0; i < indexes.length; i++) {
+            statement.setString(indexes[i], value);
+        }
+    }
+
+
+    /**
+     * Sets a parameter.
+     * @param name  parameter name
+     * @param value parameter value
+     * @throws SQLException if an error occurred
+     * @throws IllegalArgumentException if the parameter does not exist
+     * @see PreparedStatement#setInt(int, int)
+     */
+    public void setInt(String name, int value) throws SQLException {
+        int[] indexes=getIndexes(name);
+        for(int i=0; i < indexes.length; i++) {
+            statement.setInt(indexes[i], value);
+        }
+    }
+
+
+    /**
+     * Sets a parameter.
+     * @param name  parameter name
+     * @param value parameter value
+     * @throws SQLException if an error occurred
+     * @throws IllegalArgumentException if the parameter does not exist
+     * @see PreparedStatement#setInt(int, int)
+     */
+    public void setLong(String name, long value) throws SQLException {
+        int[] indexes=getIndexes(name);
+        for(int i=0; i < indexes.length; i++) {
+            statement.setLong(indexes[i], value);
+        }
+    }
+
+
+    /**
+     * Sets a parameter.
+     * @param name  parameter name
+     * @param value parameter value
+     * @throws SQLException if an error occurred
+     * @throws IllegalArgumentException if the parameter does not exist
+     * @see PreparedStatement#setTimestamp(int, java.sql.Timestamp)
+     */
+    public void setTimestamp(String name, Timestamp value) throws SQLException 
+{
+        int[] indexes=getIndexes(name);
+        for(int i=0; i < indexes.length; i++) {
+                // TODO: Con setTimestamp contra Oracle en algunos casos los queries se quedaban colgados ...
+            //statement.setTimestamp(indexes[i], value);
+                Date date = new Date(value.getTime());
+            statement.setDate(indexes[i], date);
+        }
+    }
+
+
+    /**
+     * Returns the underlying statement.
+     * @return the statement
+     */
+    public PreparedStatement getStatement() {
+        return statement;
+    }
+
+
+    /**
+     * Executes the statement.
+     * @return true if the first result is a {@link ResultSet}
+     * @throws SQLException if an error occurred
+     * @see PreparedStatement#execute()
+     */
+    public boolean execute() throws SQLException {
+        return statement.execute();
+    }
+
+
+    /**
+     * Executes the statement, which must be a query.
+     * @return the query results
+     * @throws SQLException if an error occurred
+     * @see PreparedStatement#executeQuery()
+     */
+    public ResultSet executeQuery() throws SQLException {
+        statement.setFetchSize(1000);
+        return statement.executeQuery();
+    }
+
+
+    /**
+     * Executes the statement, which must be an SQL INSERT, UPDATE or DELETE statement;
+     * or an SQL statement that returns nothing, such as a DDL statement.
+     * @return number of rows affected
+     * @throws SQLException if an error occurred
+     * @see PreparedStatement#executeUpdate()
+     */
+    public int executeUpdate() throws SQLException {
+        return statement.executeUpdate();
+    }
+
+
+    /**
+     * Closes the statement.
+     * @throws SQLException if an error occurred
+     * @see Statement#close()
+     */
+    public void close() throws SQLException {
+        statement.close();
+    }
+
+
+    /**
+     * Adds the current set of parameters as a batch entry.
+     * @throws SQLException if something went wrong
+     */
+    public void addBatch() throws SQLException {
+        statement.addBatch();
+    }
+
+
+    /**
+     * Executes all of the batched statements.
+     * 
+     * See {@link Statement#executeBatch()} for details.
+     * @return update counts for each statement
+     * @throws SQLException if something went wrong
+     */
+    public int[] executeBatch() throws SQLException {
+        return statement.executeBatch();
+    }
+}
\ No newline at end of file
diff --git a/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/Param.java b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/Param.java
new file mode 100644
index 000000000..57da13cf1
--- /dev/null
+++ b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/Param.java
@@ -0,0 +1,6 @@
+package de.hdm_stuttgart.mi.sda2.usermanage.rdbms;
+
+public interface Param {
+	public String getSql();
+	public int getIndex();
+}
diff --git a/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/UserParam.java b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/UserParam.java
new file mode 100644
index 000000000..6b39ffa5a
--- /dev/null
+++ b/Sda2/P/UnixSqlLdap/Jndi/usermanage/src/main/java/de/hdm_stuttgart/mi/sda2/usermanage/rdbms/UserParam.java
@@ -0,0 +1,30 @@
+package de.hdm_stuttgart.mi.sda2.usermanage.rdbms;
+
+public enum UserParam implements Param {
+
+	// See index values in subsequent SQL statement
+	uid(1)
+	,uidNumber(2)
+	,primaryGidNumber(3)
+	,cname(4)
+	,homeDirectory(5)
+	,shell(6)
+	;
+	
+	public static final String sql = "INSERT INTO SysUser  ( uid,  uidNumber,  primaryGidNumber,  cname,  homeDirectory,  shell) "
+									  	  /* Parameter index   1           2                  3       4               5       6  */
+                                                    + "VALUES (?,          ?,                 ?,      ?,              ?,      ?)";
+
+	public final int index;
+	
+	UserParam(final int index){
+		this.index = index;
+	}
+	public int getIndex() {
+		return index;
+	}
+	
+	public String getSql() {
+		return sql;
+	}
+}
-- 
GitLab