DetailsActivity: Check for authentication here too.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / server / database / RelatedListNavigation.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.ResultSetMetaData;
25 import java.sql.SQLException;
26 import java.sql.Statement;
27 import java.util.ArrayList;
28 import java.util.List;
29
30 import org.apache.commons.lang3.StringUtils;
31 import org.glom.web.server.Log;
32 import org.glom.web.server.SqlUtils;
33 import org.glom.web.server.Utils;
34 import org.glom.web.server.libglom.Document;
35 import org.glom.web.shared.NavigationRecord;
36 import org.glom.web.shared.TypedDataItem;
37 import org.glom.web.shared.libglom.Field;
38 import org.glom.web.shared.libglom.Field.GlomFieldType;
39 import org.glom.web.shared.libglom.layout.LayoutItemField;
40 import org.glom.web.shared.libglom.layout.LayoutItemPortal;
41 import org.glom.web.shared.libglom.layout.TableToViewDetails;
42
43 import com.mchange.v2.c3p0.ComboPooledDataSource;
44
45 /**
46  *
47  */
48 public class RelatedListNavigation extends DBAccess {
49
50         private LayoutItemPortal portal;
51
52         public RelatedListNavigation(final Document document, final String documentID, final ComboPooledDataSource cpds,
53                         final String tableName, final LayoutItemPortal portal) {
54                 super(document, documentID, cpds, tableName);
55
56                 if (portal == null) {
57                         Log.error(documentID, tableName, "portal is null in table \"" + tableName + "\". "
58                                         + "Cannot retrieve data for the related list.");
59                         return;
60                 }
61
62                 this.portal = portal;
63         }
64
65         /*
66          * Gets a NavigationRecord for the related list given the primaryKeyValue.
67          * 
68          * This code was ported from Glom: Box_Data_Portal::get_suitable_record_to_view_details()
69          */
70         public NavigationRecord getNavigationRecord(final TypedDataItem primaryKeyValue) {
71
72                 if (portal == null) {
73                         Log.error(documentID, tableName,
74                                         "The related list navigation cannot be determined because the LayoutItemPortal has not been found.");
75                         return null;
76                 }
77
78                 if (primaryKeyValue == null) {
79                         Log.error(documentID, tableName,
80                                         "The related list navigation cannot be determined because the primaryKeyValue is null.");
81                         return null;
82                 }
83
84                 final TableToViewDetails navigationTable = document.getPortalSuitableTableToViewDetails(portal);
85                 if (navigationTable == null) {
86                         Log.error(documentID, tableName,
87                                         "The related list navigation cannot cannot be determined because the navigation table details are empty.");
88                         return null;
89                 }
90
91                 if (StringUtils.isEmpty(navigationTable.tableName)) {
92                         Log.error(documentID, tableName,
93                                         "The related list navigation cannot cannot be determined because the navigation table name is empty.");
94                         return null;
95                 }
96
97                 // Get the primary key of that table:
98                 final Field navigationTablePrimaryKey = document.getTablePrimaryKeyField(navigationTable.tableName);
99
100                 // Build a layout item to get the field's value:
101                 final LayoutItemField navigationRelationshipItem = new LayoutItemField();
102                 navigationRelationshipItem.setName(navigationTablePrimaryKey.getName());
103                 navigationRelationshipItem.setFullFieldDetails(navigationTablePrimaryKey);
104                 if (navigationTable.usesRelationship != null) {
105                         navigationRelationshipItem.setRelationship(navigationTable.usesRelationship.getRelationship());
106                         navigationRelationshipItem
107                                         .setRelatedRelationship(navigationTable.usesRelationship.getRelatedRelationship());
108                 }
109
110                 // Get the value of the navigation related primary key:
111                 final List<LayoutItemField> fieldsToGet = new ArrayList<LayoutItemField>();
112                 fieldsToGet.add(navigationRelationshipItem);
113
114                 // For instance "invoice_line_id" if this is a portal to an "invoice_lines" table:
115                 final String relatedTableName = portal.getTableUsed("" /* not relevant */);
116                 final Field primaryKeyField = document.getTablePrimaryKeyField(relatedTableName);
117                 if (primaryKeyField == null) {
118                         Log.error(documentID, tableName,
119                                         "The related table's primary key field could not be found, for related table " + relatedTableName);
120                         return null;
121                 }
122
123                 final NavigationRecord navigationRecord = new NavigationRecord();
124                 String query = null;
125                 Connection conn = null;
126                 Statement st = null;
127                 ResultSet rs = null;
128                 try {
129                         // Setup the JDBC driver and get the query.
130                         conn = cpds.getConnection();
131                         st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
132
133                         if (primaryKeyValue != null) {
134                                 
135                                 // Make sure that the value knows its actual type,
136                                 // in case it was received via a URL parameter as a string representation:
137                                 Utils.transformUnknownToActualType(primaryKeyValue, primaryKeyField.getGlomType());
138
139                                 query = SqlUtils.buildSqlSelectWithKey(relatedTableName, fieldsToGet, primaryKeyField, primaryKeyValue);
140
141                                 rs = st.executeQuery(query);
142
143                                 // Set the output parameters:
144                                 navigationRecord.setTableName(navigationTable.tableName);
145
146                                 rs.next();
147                                 final TypedDataItem navigationTablePrimaryKeyValue = new TypedDataItem();
148                                 final ResultSetMetaData rsMetaData = rs.getMetaData();
149                                 final int queryReturnValueType = rsMetaData.getColumnType(1);
150                                 switch (navigationTablePrimaryKey.getGlomType()) {
151                                 case TYPE_NUMERIC:
152                                         if (queryReturnValueType == java.sql.Types.NUMERIC) {
153                                                 navigationTablePrimaryKeyValue.setNumber(rs.getDouble(1));
154                                         } else {
155                                                 logNavigationTablePrimaryKeyTypeMismatchError(Field.GlomFieldType.TYPE_NUMERIC,
156                                                                 rsMetaData.getColumnTypeName(1));
157                                         }
158                                         break;
159                                 case TYPE_TEXT:
160                                         if (queryReturnValueType == java.sql.Types.VARCHAR) {
161                                                 navigationTablePrimaryKeyValue.setText(rs.getString(1));
162                                         } else {
163                                                 logNavigationTablePrimaryKeyTypeMismatchError(Field.GlomFieldType.TYPE_TEXT,
164                                                                 rsMetaData.getColumnTypeName(1));
165                                         }
166                                         break;
167                                 default:
168                                         Log.error(documentID, tableName, "Unsupported java.sql.Type: " + rsMetaData.getColumnTypeName(1));
169                                         Log.error(documentID, tableName,
170                                                         "The navigation table primary key value will not be created. This is a bug.");
171                                         break;
172                                 }
173
174                                 // The value is empty when there there is no record to match the key in the related table:
175                                 // For instance, if an invoice lines record mentions a product id, but the product does not exist in the
176                                 // products table.
177                                 if (navigationTablePrimaryKeyValue.isEmpty()) {
178                                         Log.info(documentID, tableName, "SQL query returned empty primary key for navigation to the "
179                                                         + navigationTable.tableName + "table. Navigation may not work correctly");
180                                         navigationRecord.setPrimaryKeyValue(null);
181                                 } else {
182                                         navigationRecord.setPrimaryKeyValue(navigationTablePrimaryKeyValue);
183                                 }
184                         }
185                 } catch (final SQLException e) {
186                         Log.error(documentID, tableName, "Error executing database query: " + query, e);
187                         // TODO: somehow notify user of problem
188                         return null;
189                 } finally {
190                         // cleanup everything that has been used
191                         try {
192                                 if (rs != null) {
193                                         rs.close();
194                                 }
195                                 if (st != null) {
196                                         st.close();
197                                 }
198                                 if (conn != null) {
199                                         conn.close();
200                                 }
201                         } catch (final Exception e) {
202                                 Log.error(documentID, tableName,
203                                                 "Error closing database resources. Subsequent database queries may not work.", e);
204                         }
205                 }
206
207                 return navigationRecord;
208         }
209
210         private void logNavigationTablePrimaryKeyTypeMismatchError(final GlomFieldType glomType,
211                         final String queryReturnValueTypeName) {
212                 Log.error(documentID, tableName, "The expected type from the Glom document: " + glomType
213                                 + " doesn't match the type returned by the SQL query: " + queryReturnValueTypeName + ".");
214                 Log.error(documentID, tableName, "The navigation table primary key value will not be created. This is a bug.");
215
216         }
217 }