Rename PrimaryKeyItem to TypedDataItem.
[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.glom.libglom.Document;
29 import org.glom.libglom.Field;
30 import org.glom.libglom.Glom;
31 import org.glom.libglom.LayoutFieldVector;
32 import org.glom.libglom.LayoutItem_Field;
33 import org.glom.libglom.LayoutItem_Portal;
34 import org.glom.libglom.SqlBuilder;
35 import org.glom.libglom.Value;
36 import org.glom.web.server.Log;
37 import org.glom.web.server.Utils;
38 import org.glom.web.shared.NavigationRecord;
39 import org.glom.web.shared.TypedDataItem;
40
41 import com.mchange.v2.c3p0.ComboPooledDataSource;
42
43 /**
44  * @author Ben Konrath <ben@bagu.org>
45  * 
46  */
47 public class RelatedListNavigation extends DBAccess {
48
49         private LayoutItem_Portal portal;
50
51         public RelatedListNavigation(Document document, String documentID, ComboPooledDataSource cpds, String tableName,
52                         String relationshipName) {
53                 super(document, documentID, cpds, tableName);
54
55                 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(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                 StringBuffer navigationTableNameSB = new StringBuffer();
79                 LayoutItem_Field navigationRelationshipItem = new LayoutItem_Field();
80                 portal.get_suitable_table_to_view_details(navigationTableNameSB, navigationRelationshipItem, document);
81
82                 String navigationTableName = navigationTableNameSB.toString();
83                 if (navigationTableName.isEmpty()) {
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                 Field navigationTablePrimaryKey = getPrimaryKeyFieldForTable(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                 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                 String relatedTableName = portal.get_table_used("" /* not relevant */);
101                 Field primaryKey = getPrimaryKeyFieldForTable(relatedTableName);
102
103                 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                         Value gdaPrimaryKeyValue = Utils.getGdaValueForPrimaryKey(documentID, tableName,
114                                         primaryKey.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                                 SqlBuilder builder = Glom.build_sql_select_with_key(relatedTableName, fieldsToGet, primaryKey,
120                                                 gdaPrimaryKeyValue);
121                                 query = Glom.sqlbuilder_get_full_query(builder);
122
123                                 rs = st.executeQuery(query);
124
125                                 // Set the output parameters:
126                                 navigationRecord.setTableName(navigationTableName);
127
128                                 rs.next();
129                                 TypedDataItem tablePrimaryKeyValue = new TypedDataItem();
130                                 ResultSetMetaData rsMetaData = rs.getMetaData();
131                                 switch (rsMetaData.getColumnType(1)) {
132                                 case java.sql.Types.NUMERIC:
133                                         tablePrimaryKeyValue.setNumber(rs.getDouble(1));
134                                         break;
135                                 default:
136                                         Log.warn(documentID, tableName, "Unsupported java.sql.Type: " + rsMetaData.getColumnTypeName(1));
137                                         break;
138                                 }
139
140                                 // The value is empty when there there is no record to match the key in the related table:
141                                 // For instance, if an invoice lines record mentions a product id, but the product does not exist in the
142                                 // products table.
143                                 if (tablePrimaryKeyValue.isEmpty()) {
144                                         Log.info(documentID, tableName, "SQL query returned empty primary key for navigation to the "
145                                                         + navigationTableName + "table. Navigation may not work correctly");
146                                         navigationRecord.setPrimaryKeyValue(null);
147                                 } else {
148                                         navigationRecord.setPrimaryKeyValue(tablePrimaryKeyValue);
149                                 }
150                         }
151                 } catch (SQLException e) {
152                         Log.error(documentID, tableName, "Error executing database query: " + query, e);
153                         // TODO: somehow notify user of problem
154                         return null;
155                 } finally {
156                         // cleanup everything that has been used
157                         try {
158                                 if (rs != null)
159                                         rs.close();
160                                 if (st != null)
161                                         st.close();
162                                 if (conn != null)
163                                         conn.close();
164                         } catch (Exception e) {
165                                 Log.error(documentID, tableName,
166                                                 "Error closing database resources. Subsequent database queries may not work.", e);
167                         }
168                 }
169
170                 return navigationRecord;
171         }
172 }