]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
CSPACE-5943: Use prepared statements, rather than dynamically-build SQL that accepts...
authorAron Roberts <aron@socrates.berkeley.edu>
Tue, 26 Mar 2013 03:24:11 +0000 (20:24 -0700)
committerAron Roberts <aron@socrates.berkeley.edu>
Tue, 26 Mar 2013 03:24:11 +0000 (20:24 -0700)
services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityItemDocumentModelHandler.java
services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java
services/common/src/main/java/org/collectionspace/services/common/storage/PreparedStatementBuilder.java [new file with mode: 0644]
services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryJavaClientImpl.java

index d54ca7a6fa5dfb13e40cdc688191e8e90de5b45d..007dded89c3ada312a9a35142ca9780dbfeb2121 100644 (file)
@@ -153,6 +153,10 @@ public abstract class AuthorityItemDocumentModelHandler<AICommon>
     public void setInAuthority(String inAuthority) {
         this.inAuthority = inAuthority;
     }
+    
+   public String getInAuthority() {
+        return this.inAuthority;
+    }
 
     /** Subclasses may override this to customize the URI segment. */
     public String getAuthorityServicePath() {
index 8f58bd6352112672a310879cfd1861243f21a889..09b662ee44298cce7d6abd4b06db401b51059ef5 100644 (file)
@@ -30,6 +30,7 @@ import javax.sql.DataSource;
 import java.sql.DatabaseMetaData;\r
 import java.sql.Connection;\r
 import java.sql.DriverManager;\r
+import java.sql.PreparedStatement;\r
 import java.sql.ResultSet;\r
 import java.sql.ResultSetMetaData;\r
 import java.sql.SQLException;\r
@@ -55,6 +56,8 @@ public class JDBCTools {
     public static String NUXEO_MANAGER_DATASOURCE_NAME = "NuxeoMgrDS";\r
     public static String NUXEO_READER_DATASOURCE_NAME = "NuxeoReaderDS";\r
     public static String NUXEO_USER_NAME = "nuxeo";\r
+    public static String SQL_WILDCARD = "%";\r
+    \r
     //\r
     // Private constants\r
     //\r
@@ -175,14 +178,13 @@ public class JDBCTools {
             conn = getConnection(dataSourceName, repositoryName);\r
             stmt = conn.createStatement();\r
              \r
-           RowSetFactory rowSetFactory = RowSetProvider.newFactory();\r
+            RowSetFactory rowSetFactory = RowSetProvider.newFactory();\r
             CachedRowSet crs = rowSetFactory.createCachedRowSet();\r
 \r
             stmt = conn.createStatement();\r
             try (ResultSet resultSet = stmt.executeQuery(sql)) {\r
                 crs.populate(resultSet);\r
             }\r
-            stmt.close(); // also closes resultSet\r
             return crs;\r
         } catch (SQLException sqle) {\r
             SQLException tempException = sqle;\r
@@ -193,11 +195,43 @@ public class JDBCTools {
             throw new RuntimeException("SQL problem in executeQuery: ", sqle);\r
         } finally {\r
             try {\r
+                if (stmt != null) {\r
+                    stmt.close();\r
+                }\r
                 if (conn != null) {\r
                     conn.close();\r
                 }\r
-                if (stmt != null) {\r
-                    stmt.close();\r
+            } catch (SQLException sqle) {\r
+                logger.debug("SQL Exception closing statement/connection in executeQuery: " + sqle.getLocalizedMessage());\r
+                return null;\r
+            }\r
+        }\r
+    }\r
+    \r
+    public static CachedRowSet executePreparedQuery(final PreparedStatementBuilder builder,\r
+            String dataSourceName, String repositoryName, String sql) throws Exception {\r
+        Connection conn = null;\r
+        PreparedStatement ps = null;\r
+        try {\r
+            conn = getConnection(dataSourceName, repositoryName);\r
+            RowSetFactory rowSetFactory = RowSetProvider.newFactory();\r
+            CachedRowSet crs = rowSetFactory.createCachedRowSet();\r
+            ps = builder.build(conn);\r
+            // FIXME: transition this log statement to DEBUG level when appropriate\r
+            if (logger.isInfoEnabled()) {\r
+                logger.info("prepared statement=" + ps.toString());\r
+            }\r
+            try (ResultSet resultSet = ps.executeQuery()) {\r
+                crs.populate(resultSet);\r
+            }\r
+            return crs;\r
+        } finally {\r
+            try {\r
+                if (ps != null) {\r
+                    ps.close();\r
+                }\r
+                if (conn != null) {\r
+                    conn.close();\r
                 }\r
             } catch (SQLException sqle) {\r
                 logger.debug("SQL Exception closing statement/connection in executeQuery: " + sqle.getLocalizedMessage());\r
@@ -233,12 +267,12 @@ public class JDBCTools {
             throw new RuntimeException("SQL problem in executeUpdate: " + msg, sqle);\r
         } finally {\r
             try {\r
-                if (conn != null) {\r
-                    conn.close();\r
-                }\r
                 if (stmt != null) {\r
                     stmt.close();\r
                 }\r
+                if (conn != null) {\r
+                    conn.close();\r
+                }\r
             } catch (SQLException sqle) {\r
                 logger.debug("SQL Exception closing statement/connection in executeUpdate: " + sqle.getLocalizedMessage());\r
                 return -1;\r
diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/PreparedStatementBuilder.java b/services/common/src/main/java/org/collectionspace/services/common/storage/PreparedStatementBuilder.java
new file mode 100644 (file)
index 0000000..7b8f09d
--- /dev/null
@@ -0,0 +1,37 @@
+package org.collectionspace.services.common.storage;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+/**
+ * Per http://stackoverflow.com/a/7127189
+ */
+
+import java.sql.SQLException;
+
+public class PreparedStatementBuilder
+{
+    private String sql;
+
+    public PreparedStatementBuilder(final String sql) {
+        this.sql = sql;
+    }
+
+    protected void preparePrepared(final PreparedStatement preparedStatement) 
+            throws SQLException {
+        // This virtual method lets us declare how, when we generate our
+        // PreparedStatement, we want it to be set up.
+
+        // Note that at the time this method is overridden, the 
+        // PreparedStatement has not yet been created.
+    }
+
+    public PreparedStatement build(final Connection conn)
+            throws SQLException
+    {
+        // Fetch the PreparedStatement
+        final PreparedStatement returnable = conn.prepareStatement(sql);
+        // Perform setup directives
+        preparePrepared(returnable);
+        return returnable;
+    }
+}
\ No newline at end of file
index 19f7aeccd9555cedfa9b929ec7922d36b5563fdc..60c2af0dde58a703a6ba442a6cb367b3b6a1b9f0 100644 (file)
@@ -18,6 +18,7 @@ package org.collectionspace.services.nuxeo.client.java;
 
 import java.io.Serializable;
 import java.sql.Connection;
+import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
@@ -42,6 +43,7 @@ import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.query.QueryContext;
 import org.collectionspace.services.common.repository.RepositoryClient;
 import org.collectionspace.services.common.storage.JDBCTools;
+import org.collectionspace.services.common.storage.PreparedStatementBuilder;
 import org.collectionspace.services.lifecycle.TransitionDef;
 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
 
@@ -100,7 +102,7 @@ public class RepositoryJavaClientImpl implements RepositoryClient<PoxPayloadIn,
     public static final String NUXEO_CORE_TYPE_DOMAIN = "Domain";
     public static final String NUXEO_CORE_TYPE_WORKSPACEROOT = "WorkspaceRoot";
     private static final String ID_COLUMN_NAME = "id";
-
+    
     /**
      * Instantiates a new repository java client impl.
      */
@@ -908,67 +910,48 @@ public class RepositoryJavaClientImpl implements RepositoryClient<PoxPayloadIn,
         }
     }
 
-    private DocumentModelList getFilteredJDBC(RepositoryInstance repoSession, ServiceContext ctx, DocumentHandler handler, QueryContext queryContext)
-            throws Exception {
+    private DocumentModelList getFilteredJDBC(RepositoryInstance repoSession, ServiceContext ctx, 
+            DocumentHandler handler, QueryContext queryContext) throws Exception {
         DocumentModelList result = new DocumentModelListImpl();
 
         String dataSourceName = JDBCTools.NUXEO_DATASOURCE_NAME;
         String repositoryName = ctx.getRepositoryName();
 
-        // Connection connection = JDBCTools.getConnection(dataSourceName, repositoryName);
-
         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
-        String partialTerm = queryParams.getFirst(IQueryManager.SEARCH_TYPE_PARTIALTERM);
+        final String partialTerm = queryParams.getFirst(IQueryManager.SEARCH_TYPE_PARTIALTERM);
 
         // FIXME: Replace this placeholder with an appropriate per-authority value
         // obtained from the relevant document handler
-        String termInfoGroupTableName = "loctermgroup";
+        final String termGroupTableName = "loctermgroup";
+        
+        // AuthorityItemDocModelHandler authHandler = (AuthorityItemDocModelHandler) handler;
 
         // FIXME: Replace this placeholder query with an actual query from CSPACE-5945
-        // FIXME: Consider using a prepared statement here
+        
+        // IMPORTANT FIXME: Guard against SQL injection attacks, since partialTerm
+        // is obtained from user-supplied query parameters
+        // See, for example: http://stackoverflow.com/a/7127189
         String sql =
-                "SELECT DISTINCT hierarchy.id as id " // For debugging add: ", ltg.termdisplayname"
+                "SELECT DISTINCT hierarchy.id as id "
                 + " FROM hierarchy "
                 + " LEFT JOIN hierarchy h1 "
                + "   ON h1.parentid = hierarchy.id "
-               + "   AND h1.name = 'locations_common:locTermGroupList' "
-                + " LEFT JOIN loctermgroup ltg "
-               + "   ON ltg.id = h1.id "
-                + " WHERE (ltg.termdisplayname ILIKE '" + partialTerm + "%')";
-
-        // Make sure autocommit is off. See:
-        // http://jdbc.postgresql.org/documentation/80/query.html#query-with-cursor
-        // http://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html
-        // connection.setAutoCommit(false);
-
-        // FIXME: Add exception handling and 'finally' blocks to ensure we close resources
-        // FIXME: Identify whether we can piggyback on existing JDBC method(s) in common.storage,
-        // and if so, add whatever additional functionality may be required to those method(s)
-        // FIXME: Add pagination handling
-
-        /*
-         Statement st = connection.createStatement();
-         // Enable use of the cursor for pagination
-         st.setFetchSize(50);
-         List<String> docIds = new ArrayList<String>();
-         ResultSet rs = st.executeQuery(sql);
-         String id;
-         while (rs.next()) {
-         id = rs.getString("id");
-         if (Tools.notBlank(id)) {
-         docIds.add(id);
-         }
-         }
-         rs.close();
-
-         // Close the statement.
-         st.close();
-         */
+                + " LEFT JOIN ? tg "
+               + "   ON tg.id = h1.id "
+                + " WHERE tg.termdisplayname ILIKE ?";
+        
+        PreparedStatementBuilder partialTermMatchStatementBuilder = new PreparedStatementBuilder(sql){
+            @Override
+            protected void preparePrepared(PreparedStatement preparedStatement)
+                throws SQLException
+            {
+                preparedStatement.setString(1, termGroupTableName);
+                preparedStatement.setString(2, partialTerm + JDBCTools.SQL_WILDCARD);
+            }};
 
         List<String> docIds = new ArrayList<String>();
-        CachedRowSet crs = null;
-        try {
-            crs = JDBCTools.executeQuery(dataSourceName, repositoryName, sql);
+        try (CachedRowSet crs = JDBCTools.executePreparedQuery(partialTermMatchStatementBuilder,
+                dataSourceName, repositoryName, sql)) {
 
             // If the response to the query is null or contains zero rows,
             // return an empty list of document models
@@ -982,20 +965,8 @@ public class RepositoryJavaClientImpl implements RepositoryClient<PoxPayloadIn,
 
             // Otherwise, get the document IDs from the results of the query
             String id;
-            /*
-            int idColumnIndex;
-            try {
-                idColumnIndex = crs.findColumn(ID_COLUMN_NAME);
-            } catch (SQLException sqle) {
-                logger.warn("Could not find expected column '" + ID_COLUMN_NAME + "' in query results.");
-                return result; // return an empty list of document models
-            } finally {
-                crs.close();
-            }
-            */
             crs.beforeFirst();
             while (crs.next()) {
-                // id = crs.getString(idColumnIndex);
                 id = crs.getString(1);
                 if (Tools.notBlank(id)) {
                     docIds.add(id);
@@ -1004,8 +975,6 @@ public class RepositoryJavaClientImpl implements RepositoryClient<PoxPayloadIn,
         } catch (SQLException sqle) {
             logger.warn("Could not obtain document IDs via SQL query '" + sql + "': " + sqle.getMessage());
             return result; // return an empty list of document models
-        } finally {
-            crs.close();
         } 
 
         // Get a list of document models, using the IDs obtained from the query
@@ -1015,6 +984,7 @@ public class RepositoryJavaClientImpl implements RepositoryClient<PoxPayloadIn,
 
         return result;
     }
+    
 
     private DocumentModelList getFilteredCMIS(RepositoryInstance repoSession, ServiceContext ctx, DocumentHandler handler, QueryContext queryContext)
             throws DocumentNotFoundException, DocumentException {