DetailsActivity: Check for authentication here too.
[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.DriverManager;
24 import java.sql.ResultSet;
25 import java.sql.SQLException;
26 import java.sql.Statement;
27 import java.util.ArrayList;
28 import java.util.List;
29
30 import org.glom.web.server.Log;
31 import org.glom.web.server.Utils;
32 import org.glom.web.server.libglom.Document;
33 import org.glom.web.shared.DataItem;
34 import org.glom.web.shared.TypedDataItem;
35 import org.glom.web.shared.libglom.Field;
36 import org.glom.web.shared.libglom.layout.LayoutItem;
37 import org.glom.web.shared.libglom.layout.LayoutItemField;
38 import org.glom.web.shared.libglom.layout.SortClause;
39 import org.glom.web.shared.libglom.layout.UsesRelationship;
40
41 import com.mchange.v2.c3p0.ComboPooledDataSource;
42
43 /**
44  *
45  */
46 public abstract class ListDBAccess extends DBAccess {
47         protected List<LayoutItemField> fieldsToGet;
48
49         protected ListDBAccess(final Document document, final String documentID, final ComboPooledDataSource cpds,
50                         final String tableName) {
51                 super(document, documentID, cpds, tableName);
52         }
53
54         protected abstract String getSelectQuery(String quickFind, SortClause sortClause);
55
56         protected abstract String getCountQuery();
57
58         /**
59          * 
60          * @param quickFind
61          * @param start
62          * @param length
63          * @param sortColumnIndex
64          *            The index of the column to sort by, or -1 for none.
65          * @param isAscending
66          * @return
67          */
68         protected ArrayList<DataItem[]> getListData(final String quickFind, final int start, final int length,
69                         final int sortColumnIndex, final boolean isAscending) {
70
71                 // create a sort clause for the column we've been asked to sort
72                 final SortClause sortClause = new SortClause();
73                 if (sortColumnIndex != -1) {
74                         final LayoutItem item = fieldsToGet.get(sortColumnIndex);
75                         if (item instanceof LayoutItemField) {
76                                 final UsesRelationship layoutItemField = (UsesRelationship) item;
77                                 sortClause.add(new SortClause.SortField(layoutItemField, isAscending));
78                         } else {
79                                 Log.error(documentID, tableName, "Error getting LayoutItemField for column index " + sortColumnIndex
80                                                 + ". Cannot create a sort clause for this column.");
81                         }
82                 } else {
83                         // create a sort clause for the primary key if we're not asked to sort a specific column
84                         int numItems = 0;
85                         if (fieldsToGet != null) {
86                                 numItems = Utils.safeLongToInt(fieldsToGet.size());
87                         }
88
89                         for (int i = 0; i < numItems; i++) {
90                                 final LayoutItemField layoutItem = fieldsToGet.get(i);
91                                 final Field details = layoutItem.getFullFieldDetails();
92                                 if (details != null && details.getPrimaryKey()) {
93                                         sortClause.add(new SortClause.SortField(layoutItem, true)); // ascending
94                                         break;
95                                 }
96                         }
97                 }
98
99                 ArrayList<DataItem[]> rowsList = new ArrayList<DataItem[]>();
100                 Connection conn = null;
101                 Statement st = null;
102                 ResultSet rs = null;
103                 try {
104                         
105                         //Change the timeout, because it otherwise takes ages to fail sometimes when the details are not setup.
106                         //This is more than enough.
107                         DriverManager.setLoginTimeout(5);
108
109                         // Setup the JDBC driver and get the query. Special care needs to be take to ensure that the results will be
110                         // based on a cursor so that large amounts of memory are not consumed when the query retrieve a large amount
111                         // of data. Here's the relevant PostgreSQL documentation:
112                         // http://jdbc.postgresql.org/documentation/83/query.html#query-with-cursor
113                         conn = cpds.getConnection();
114                         if(conn == null) {
115                                 Log.error(documentID, tableName, "The connection is null.");
116                                 return rowsList;
117                         }
118                         
119                         conn.setAutoCommit(false);
120                         st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
121                         st.setFetchSize(length);
122                         final String query = getSelectQuery(quickFind, sortClause) + " OFFSET " + start;
123                         // TODO Test memory usage before and after we execute the query that would result in a large ResultSet.
124                         // We need to ensure that the JDBC driver is in fact returning a cursor based result set that has a low
125                         // memory footprint. Check the difference between this value before and after the query:
126                         // Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()
127                         // Test the execution time at the same time (see the todo item in getLayoutListTable()).
128                         rs = st.executeQuery(query);
129
130                         // get the results from the ResultSet
131                         final TypedDataItem primaryKeyValue = null; //TODO: Discover it for each row instead.
132                         rowsList = convertResultSetToDTO(length, fieldsToGet, primaryKeyValue, rs);
133                 } catch (final SQLException e) {
134                         Log.error(documentID, tableName, "Error executing database query.", e);
135                         // TODO: somehow notify user of problem
136                 } finally {
137                         // cleanup everything that has been used
138                         try {
139                                 if (rs != null) {
140                                         rs.close();
141                                 }
142                                 if (st != null) {
143                                         st.close();
144                                 }
145                                 if (conn != null) {
146                                         conn.close();
147                                 }
148                         } catch (final Exception e) {
149                                 Log.error(documentID, tableName,
150                                                 "Error closing database resources. Subsequent database queries may not work.", e);
151                         }
152                 }
153                 return rowsList;
154         }
155
156         /*
157          * Get the number of rows a query with the table name and layout fields would return. This is needed for the /* list
158          * view pager.
159          */
160         protected int getResultSizeOfSQLQuery() {
161
162                 Connection conn = null;
163                 Statement st = null;
164                 ResultSet rs = null;
165                 try {
166                         //Change the timeout, because it otherwise takes ages to fail sometimes when the details are not setup.
167                         //This is more than enough.
168                         DriverManager.setLoginTimeout(5); 
169                         
170                         // Setup and execute the count query. Special care needs to be take to ensure that the results will be based
171                         // on a cursor so that large amounts of memory are not consumed when the query retrieve a large amount of
172                         // data. Here's the relevant PostgreSQL documentation:
173                         // http://jdbc.postgresql.org/documentation/83/query.html#query-with-cursor
174                         conn = cpds.getConnection();
175                         conn.setAutoCommit(false);
176                         st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
177                         final String query = getCountQuery();
178
179                         // TODO Test execution time of this query with when the number of rows in the table is large (say >
180                         // 1,000,000). Test memory usage at the same time (see the todo item in getTableData()).
181                         rs = st.executeQuery(query);
182
183                         // get the number of rows in the query
184                         rs.next();
185                         return rs.getInt(1);
186
187                 } catch (final SQLException e) {
188                         Log.error(documentID, tableName, "Error calculating number of rows in the query.", e);
189                         return -1;
190                 } finally {
191                         // cleanup everything that has been used
192                         try {
193                                 if (rs != null) {
194                                         rs.close();
195                                 }
196                                 if (st != null) {
197                                         st.close();
198                                 }
199                                 if (conn != null) {
200                                         conn.close();
201                                 }
202                         } catch (final Exception e) {
203                                 Log.error(documentID, tableName,
204                                                 "Error closing database resources. Subsequent database queries may not work.", e);
205                         }
206                 }
207         }
208
209 }