Move use of Glom.build_sql*() into a new SqlUtils class.
[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
28 import org.apache.commons.lang3.StringUtils;
29 import org.glom.libglom.Document;
30 import org.glom.libglom.Field;
31 import org.glom.libglom.Field.glom_field_type;
32 import org.glom.libglom.LayoutFieldVector;
33 import org.glom.libglom.LayoutItem_Field;
34 import org.glom.libglom.LayoutItem_Portal;
35 import org.glom.libglom.Value;
36 import org.glom.web.server.Log;
37 import org.glom.web.server.SqlUtils;
38 import org.glom.web.server.Utils;
39 import org.glom.web.shared.NavigationRecord;
40 import org.glom.web.shared.TypedDataItem;
41
42 import com.mchange.v2.c3p0.ComboPooledDataSource;
43
44 /**
45  *
46  */
47 public class RelatedListNavigation extends DBAccess {
48
49         private LayoutItem_Portal portal;
50
51         public RelatedListNavigation(final Document document, final String documentID, final ComboPooledDataSource cpds,
52                         final String tableName, final String relationshipName) {
53                 super(document, documentID, cpds, tableName);
54
55                 final LayoutItem_Portal portal = getPortal(relationshipName);
56                 if (portal == null) {
57                         Log.error(documentID, tableName, "Couldn't find LayoutItem_Portal \"" + relationshipName + "\" in table \""
58                                         + tableName + "\". " + "Cannot retrive 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 LayoutItem_Portal has not been found.");
75                         return null;
76                 }
77
78                 final StringBuffer navigationTableNameSB = new StringBuffer();
79                 final LayoutItem_Field navigationRelationshipItem = new LayoutItem_Field();
80                 portal.get_suitable_table_to_view_details(navigationTableNameSB, navigationRelationshipItem, document);
81
82                 final String navigationTableName = navigationTableNameSB.toString();
83                 if (StringUtils.isEmpty(navigationTableName)) {
84                         Log.error(documentID, tableName,
85                                         "The related list navigation cannot cannot be determined because the navigation table name is empty.");
86                         return null;
87                 }
88
89                 // Get the primary key of that table:
90                 final Field navigationTablePrimaryKey = getPrimaryKeyField(navigationTableName);
91
92                 // Build a layout item to get the field's value:
93                 navigationRelationshipItem.set_full_field_details(navigationTablePrimaryKey);
94
95                 // Get the value of the navigation related primary key:
96                 final LayoutFieldVector fieldsToGet = new LayoutFieldVector();
97                 fieldsToGet.add(navigationRelationshipItem);
98
99                 // For instance "invoice_line_id" if this is a portal to an "invoice_lines" table:
100                 final String relatedTableName = portal.get_table_used("" /* not relevant */);
101                 final Field primaryKeyField = getPrimaryKeyField(relatedTableName);
102
103                 final NavigationRecord navigationRecord = new NavigationRecord();
104                 String query = null;
105                 Connection conn = null;
106                 Statement st = null;
107                 ResultSet rs = null;
108                 try {
109                         // Setup the JDBC driver and get the query.
110                         conn = cpds.getConnection();
111                         st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
112
113                         final Value gdaPrimaryKeyValue = Utils.getGlomTypeGdaValueForTypedDataItem(documentID, tableName,
114                                         primaryKeyField.get_glom_type(), primaryKeyValue);
115
116                         // Only create the query if we've created a Gda Value from the DataItem.
117                         if (gdaPrimaryKeyValue != null) {
118
119                                 query = SqlUtils.build_sql_select_with_key(relatedTableName, fieldsToGet, primaryKeyField,
120                                                 gdaPrimaryKeyValue);
121
122                                 rs = st.executeQuery(query);
123
124                                 // Set the output parameters:
125                                 navigationRecord.setTableName(navigationTableName);
126
127                                 rs.next();
128                                 final TypedDataItem navigationTablePrimaryKeyValue = new TypedDataItem();
129                                 final ResultSetMetaData rsMetaData = rs.getMetaData();
130                                 final int queryReturnValueType = rsMetaData.getColumnType(1);
131                                 switch (navigationTablePrimaryKey.get_glom_type()) {
132                                 case TYPE_NUMERIC:
133                                         if (queryReturnValueType == java.sql.Types.NUMERIC) {
134                                                 navigationTablePrimaryKeyValue.setNumber(rs.getDouble(1));
135                                         } else {
136                                                 logNavigationTablePrimaryKeyTypeMismatchError(Field.glom_field_type.TYPE_NUMERIC,
137                                                                 rsMetaData.getColumnTypeName(1));
138                                         }
139                                         break;
140                                 case TYPE_TEXT:
141                                         if (queryReturnValueType == java.sql.Types.VARCHAR) {
142                                                 navigationTablePrimaryKeyValue.setText(rs.getString(1));
143                                         } else {
144                                                 logNavigationTablePrimaryKeyTypeMismatchError(Field.glom_field_type.TYPE_TEXT,
145                                                                 rsMetaData.getColumnTypeName(1));
146                                         }
147                                         break;
148                                 default:
149                                         Log.error(documentID, tableName, "Unsupported java.sql.Type: " + rsMetaData.getColumnTypeName(1));
150                                         Log.error(documentID, tableName,
151                                                         "The navigation table primary key value will not be created. This is a bug.");
152                                         break;
153                                 }
154
155                                 // The value is empty when there there is no record to match the key in the related table:
156                                 // For instance, if an invoice lines record mentions a product id, but the product does not exist in the
157                                 // products table.
158                                 if (navigationTablePrimaryKeyValue.isEmpty()) {
159                                         Log.info(documentID, tableName, "SQL query returned empty primary key for navigation to the "
160                                                         + navigationTableName + "table. Navigation may not work correctly");
161                                         navigationRecord.setPrimaryKeyValue(null);
162                                 } else {
163                                         navigationRecord.setPrimaryKeyValue(navigationTablePrimaryKeyValue);
164                                 }
165                         }
166                 } catch (final SQLException e) {
167                         Log.error(documentID, tableName, "Error executing database query: " + query, e);
168                         // TODO: somehow notify user of problem
169                         return null;
170                 } finally {
171                         // cleanup everything that has been used
172                         try {
173                                 if (rs != null)
174                                         rs.close();
175                                 if (st != null)
176                                         st.close();
177                                 if (conn != null)
178                                         conn.close();
179                         } catch (final Exception e) {
180                                 Log.error(documentID, tableName,
181                                                 "Error closing database resources. Subsequent database queries may not work.", e);
182                         }
183                 }
184
185                 return navigationRecord;
186         }
187
188         private void logNavigationTablePrimaryKeyTypeMismatchError(final glom_field_type glomType,
189                         final String queryReturnValueTypeName) {
190                 Log.error(documentID, tableName, "The expected type from the Glom document: " + glomType
191                                 + " doesn't match the type returned by the SQL query: " + queryReturnValueTypeName + ".");
192                 Log.error(documentID, tableName, "The navigation table primary key value will not be created. This is a bug.");
193
194         }
195 }