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