Fix portal navigation via the Details button.
[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.libglom.Document;
34 import org.glom.web.server.libglom.Document.TableToViewDetails;
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
42 import com.mchange.v2.c3p0.ComboPooledDataSource;
43
44 /**
45  *
46  */
47 public class RelatedListNavigation extends DBAccess {
48
49         private LayoutItemPortal 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 LayoutItemPortal portal = getPortal(relationshipName);
56                 if (portal == null) {
57                         Log.error(documentID, tableName, "Couldn't find LayoutItemPortal \"" + 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 LayoutItemPortal has not been found.");
75                         return null;
76                 }
77
78                 final StringBuffer navigationTableNameSB = new StringBuffer();
79                 final LayoutItemField navigationRelationshipItem = new LayoutItemField();
80
81                 final TableToViewDetails toViewDetails = document.getPortalSuitableTableToViewDetails(portal);
82                 final String navigationTableName = toViewDetails.tableName;
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.setFullFieldDetails(navigationTablePrimaryKey);
94
95                 // Get the value of the navigation related primary key:
96                 final List<LayoutItemField> fieldsToGet = new ArrayList<LayoutItemField>();
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.getTableUsed("" /* 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                         if (primaryKeyValue != null) {
114
115                                 query = SqlUtils.build_sql_select_with_key(conn, relatedTableName, fieldsToGet, primaryKeyField,
116                                                 primaryKeyValue);
117
118                                 rs = st.executeQuery(query);
119
120                                 // Set the output parameters:
121                                 navigationRecord.setTableName(navigationTableName);
122
123                                 rs.next();
124                                 final TypedDataItem navigationTablePrimaryKeyValue = new TypedDataItem();
125                                 final ResultSetMetaData rsMetaData = rs.getMetaData();
126                                 final int queryReturnValueType = rsMetaData.getColumnType(1);
127                                 switch (navigationTablePrimaryKey.getGlomType()) {
128                                 case TYPE_NUMERIC:
129                                         if (queryReturnValueType == java.sql.Types.NUMERIC) {
130                                                 navigationTablePrimaryKeyValue.setNumber(rs.getDouble(1));
131                                         } else {
132                                                 logNavigationTablePrimaryKeyTypeMismatchError(Field.GlomFieldType.TYPE_NUMERIC,
133                                                                 rsMetaData.getColumnTypeName(1));
134                                         }
135                                         break;
136                                 case TYPE_TEXT:
137                                         if (queryReturnValueType == java.sql.Types.VARCHAR) {
138                                                 navigationTablePrimaryKeyValue.setText(rs.getString(1));
139                                         } else {
140                                                 logNavigationTablePrimaryKeyTypeMismatchError(Field.GlomFieldType.TYPE_TEXT,
141                                                                 rsMetaData.getColumnTypeName(1));
142                                         }
143                                         break;
144                                 default:
145                                         Log.error(documentID, tableName, "Unsupported java.sql.Type: " + rsMetaData.getColumnTypeName(1));
146                                         Log.error(documentID, tableName,
147                                                         "The navigation table primary key value will not be created. This is a bug.");
148                                         break;
149                                 }
150
151                                 // The value is empty when there there is no record to match the key in the related table:
152                                 // For instance, if an invoice lines record mentions a product id, but the product does not exist in the
153                                 // products table.
154                                 if (navigationTablePrimaryKeyValue.isEmpty()) {
155                                         Log.info(documentID, tableName, "SQL query returned empty primary key for navigation to the "
156                                                         + navigationTableName + "table. Navigation may not work correctly");
157                                         navigationRecord.setPrimaryKeyValue(null);
158                                 } else {
159                                         navigationRecord.setPrimaryKeyValue(navigationTablePrimaryKeyValue);
160                                 }
161                         }
162                 } catch (final SQLException e) {
163                         Log.error(documentID, tableName, "Error executing database query: " + query, e);
164                         // TODO: somehow notify user of problem
165                         return null;
166                 } finally {
167                         // cleanup everything that has been used
168                         try {
169                                 if (rs != null)
170                                         rs.close();
171                                 if (st != null)
172                                         st.close();
173                                 if (conn != null)
174                                         conn.close();
175                         } catch (final Exception e) {
176                                 Log.error(documentID, tableName,
177                                                 "Error closing database resources. Subsequent database queries may not work.", e);
178                         }
179                 }
180
181                 return navigationRecord;
182         }
183
184         private void logNavigationTablePrimaryKeyTypeMismatchError(final GlomFieldType glomType,
185                         final String queryReturnValueTypeName) {
186                 Log.error(documentID, tableName, "The expected type from the Glom document: " + glomType
187                                 + " doesn't match the type returned by the SQL query: " + queryReturnValueTypeName + ".");
188                 Log.error(documentID, tableName, "The navigation table primary key value will not be created. This is a bug.");
189
190         }
191 }