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