Initial Document loading implementation, instead of libglom.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / server / database / ListDBAccess.java
1 /*
2  * Copyright (C) 2011 Openismus GmbH
3  *
4  * This file is part of GWT-Glom.
5  *
6  * GWT-Glom is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with GWT-Glom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package org.glom.web.server.database;
21
22 import java.sql.Connection;
23 import java.sql.ResultSet;
24 import java.sql.SQLException;
25 import java.sql.Statement;
26 import java.util.ArrayList;
27
28 import org.glom.libglom.Field;
29 import org.glom.libglom.LayoutFieldVector;
30 import org.glom.libglom.LayoutItem_Field;
31 import org.glom.libglom.SortClause;
32 import org.glom.libglom.SortFieldPair;
33 import org.glom.web.server.Log;
34 import org.glom.web.server.Utils;
35 import org.glom.web.shared.DataItem;
36 import org.glom.web.shared.libglom.Document;
37
38 import com.mchange.v2.c3p0.ComboPooledDataSource;
39
40 /**
41  *
42  */
43 public abstract class ListDBAccess extends DBAccess {
44         protected LayoutFieldVector fieldsToGet;
45
46         protected ListDBAccess(final Document document, final String documentID, final ComboPooledDataSource cpds,
47                         final String tableName) {
48                 super(document, documentID, cpds, tableName);
49         }
50
51         protected abstract String getSelectQuery(Connection connection, String quickFind, SortClause sortClause);
52
53         protected abstract String getCountQuery(Connection connection);
54
55         protected ArrayList<DataItem[]> getListData(final String quickFind, final int start, final int length,
56                         final boolean useSortClause, final int sortColumnIndex, final boolean isAscending) {
57
58                 // create a sort clause for the column we've been asked to sort
59                 final SortClause sortClause = new SortClause();
60                 if (useSortClause) {
61                         final org.glom.libglom.LayoutItem item = fieldsToGet.get(sortColumnIndex);
62                         final LayoutItem_Field layoutItemField = LayoutItem_Field.cast_dynamic(item);
63                         if (layoutItemField != null)
64                                 sortClause.add(new SortFieldPair(layoutItemField, isAscending));
65                         else {
66                                 Log.error(documentID, tableName, "Error getting LayoutItem_Field for column index " + sortColumnIndex
67                                                 + ". Cannot create a sort clause for this column.");
68                         }
69                 } else {
70                         // create a sort clause for the primary key if we're not asked to sort a specific column
71                         final int numItems = Utils.safeLongToInt(fieldsToGet.size());
72                         for (int i = 0; i < numItems; i++) {
73                                 final LayoutItem_Field layoutItem = fieldsToGet.get(i);
74                                 final Field details = layoutItem.get_full_field_details();
75                                 if (details != null && details.get_primary_key()) {
76                                         sortClause.add(new SortFieldPair(layoutItem, true)); // ascending
77                                         break;
78                                 }
79                         }
80                 }
81
82                 ArrayList<DataItem[]> rowsList = new ArrayList<DataItem[]>();
83                 Connection conn = null;
84                 Statement st = null;
85                 ResultSet rs = null;
86                 try {
87                         // Setup the JDBC driver and get the query. Special care needs to be take to ensure that the results will be
88                         // based on a cursor so that large amounts of memory are not consumed when the query retrieve a large amount
89                         // of data. Here's the relevant PostgreSQL documentation:
90                         // http://jdbc.postgresql.org/documentation/83/query.html#query-with-cursor
91                         conn = cpds.getConnection();
92                         conn.setAutoCommit(false);
93                         st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
94                         st.setFetchSize(length);
95                         final String query = getSelectQuery(conn, quickFind, sortClause) + " OFFSET " + start;
96                         // TODO Test memory usage before and after we execute the query that would result in a large ResultSet.
97                         // We need to ensure that the JDBC driver is in fact returning a cursor based result set that has a low
98                         // memory footprint. Check the difference between this value before and after the query:
99                         // Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()
100                         // Test the execution time at the same time (see the todo item in getLayoutListTable()).
101                         rs = st.executeQuery(query);
102
103                         // get the results from the ResultSet
104                         rowsList = convertResultSetToDTO(length, fieldsToGet, rs);
105                 } catch (final SQLException e) {
106                         Log.error(documentID, tableName, "Error executing database query.", e);
107                         // TODO: somehow notify user of problem
108                 } finally {
109                         // cleanup everything that has been used
110                         try {
111                                 if (rs != null)
112                                         rs.close();
113                                 if (st != null)
114                                         st.close();
115                                 if (conn != null)
116                                         conn.close();
117                         } catch (final Exception e) {
118                                 Log.error(documentID, tableName,
119                                                 "Error closing database resources. Subsequent database queries may not work.", e);
120                         }
121                 }
122                 return rowsList;
123         }
124
125         /*
126          * Get the number of rows a query with the table name and layout fields would return. This is needed for the /* list
127          * view pager.
128          */
129         protected int getResultSizeOfSQLQuery() {
130
131                 Connection conn = null;
132                 Statement st = null;
133                 ResultSet rs = null;
134                 try {
135                         // Setup and execute the count query. Special care needs to be take to ensure that the results will be based
136                         // on a cursor so that large amounts of memory are not consumed when the query retrieve a large amount of
137                         // data. Here's the relevant PostgreSQL documentation:
138                         // http://jdbc.postgresql.org/documentation/83/query.html#query-with-cursor
139                         conn = cpds.getConnection();
140                         conn.setAutoCommit(false);
141                         st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
142                         final String query = getCountQuery(conn);
143                         // TODO Test execution time of this query with when the number of rows in the table is large (say >
144                         // 1,000,000). Test memory usage at the same time (see the todo item in getTableData()).
145                         rs = st.executeQuery(query);
146
147                         // get the number of rows in the query
148                         rs.next();
149                         return rs.getInt(1);
150
151                 } catch (final SQLException e) {
152                         Log.error(documentID, tableName, "Error calculating number of rows in the query.", e);
153                         return -1;
154                 } finally {
155                         // cleanup everything that has been used
156                         try {
157                                 if (rs != null)
158                                         rs.close();
159                                 if (st != null)
160                                         st.close();
161                                 if (conn != null)
162                                         conn.close();
163                         } catch (final Exception e) {
164                                 Log.error(documentID, tableName,
165                                                 "Error closing database resources. Subsequent database queries may not work.", e);
166                         }
167                 }
168         }
169
170 }