Don't use Strings to hold primary key values.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / server / OnlineGlomServiceImpl.java
1 /*
2  * Copyright (C) 2010, 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;
21
22 import java.beans.PropertyVetoException;
23 import java.io.File;
24 import java.io.FilenameFilter;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.sql.SQLException;
28 import java.util.ArrayList;
29 import java.util.Hashtable;
30 import java.util.Properties;
31
32 import javax.servlet.ServletException;
33
34 import org.glom.libglom.BakeryDocument.LoadFailureCodes;
35 import org.glom.libglom.Document;
36 import org.glom.libglom.Glom;
37 import org.glom.web.client.OnlineGlomService;
38 import org.glom.web.shared.DataItem;
39 import org.glom.web.shared.DetailsLayoutAndData;
40 import org.glom.web.shared.DocumentInfo;
41 import org.glom.web.shared.Documents;
42 import org.glom.web.shared.NavigationRecord;
43 import org.glom.web.shared.PrimaryKeyItem;
44 import org.glom.web.shared.layout.LayoutGroup;
45
46 import com.google.gwt.user.server.rpc.RemoteServiceServlet;
47 import com.mchange.v2.c3p0.DataSources;
48
49 /**
50  * The servlet class for setting up the server side of Online Glom. The public methods in this class are the methods
51  * that can be called by the client side code.
52  * 
53  * @author Ben Konrath <ben@bagu.org>
54  */
55 @SuppressWarnings("serial")
56 public class OnlineGlomServiceImpl extends RemoteServiceServlet implements OnlineGlomService {
57
58         private static final String GLOM_FILE_EXTENSION = ".glom";
59
60         // convenience class to for dealing with the Online Glom configuration file
61         private class OnlineGlomProperties extends Properties {
62                 public String getKey(String value) {
63                         for (String key : stringPropertyNames()) {
64                                 if (getProperty(key).trim().equals(value))
65                                         return key;
66                         }
67                         return null;
68                 }
69         }
70
71         private final Hashtable<String, ConfiguredDocument> documentMapping = new Hashtable<String, ConfiguredDocument>();
72
73         /*
74          * This is called when the servlet is started or restarted.
75          * 
76          * (non-Javadoc)
77          * 
78          * @see javax.servlet.GenericServlet#init()
79          */
80         @Override
81         public void init() throws ServletException {
82                 // Find the configuration file. See this thread for background info:
83                 // http://stackoverflow.com/questions/2161054/where-to-place-properties-files-in-a-jsp-servlet-web-application
84                 // FIXME move onlineglom.properties to the WEB-INF folder (option number 2 from the stackoverflow question)
85                 OnlineGlomProperties config = new OnlineGlomProperties();
86                 InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("onlineglom.properties");
87                 if (is == null) {
88                         Log.fatal("onlineglom.properties not found.");
89                         throw new ServletException("onlineglom.properties not found.");
90                 }
91                 try {
92                         config.load(is);
93                 } catch (IOException e) {
94                         throw new ServletException(e.getMessage(), e);
95                 }
96
97                 // check if we can read the configured glom file directory
98                 String documentDirName = config.getProperty("glom.document.directory");
99                 File documentDir = new File(documentDirName);
100                 if (!documentDir.isDirectory()) {
101                         Log.fatal(documentDirName + " is not a directory.");
102                         throw new ServletException(documentDirName + " is not a directory.");
103                 }
104                 if (!documentDir.canRead()) {
105                         Log.fatal("Can't read the files in : " + documentDirName);
106                         throw new ServletException("Can't read the files in : " + documentDirName);
107                 }
108
109                 // get and check the glom files in the specified directory
110                 File[] glomFiles = documentDir.listFiles(new FilenameFilter() {
111                         @Override
112                         public boolean accept(File dir, String name) {
113                                 return name.endsWith(GLOM_FILE_EXTENSION);
114                         }
115                 });
116
117                 // don't continue if there aren't any Glom files to configure
118                 if (glomFiles.length <= 0) {
119                         Log.error("Unable to find any Glom documents in the configured directory: " + documentDirName);
120                         Log.error("Check the onlineglom.properties file to ensure that 'glom.document.directory' is set to the correct directory.");
121                         return;
122                 }
123
124                 Glom.libglom_init();
125                 for (File glomFile : glomFiles) {
126                         Document document = new Document();
127                         document.set_file_uri("file://" + glomFile.getAbsolutePath());
128                         int error = 0;
129                         boolean retval = document.load(error);
130                         if (retval == false) {
131                                 String message;
132                                 if (LoadFailureCodes.LOAD_FAILURE_CODE_NOT_FOUND == LoadFailureCodes.swigToEnum(error)) {
133                                         message = "Could not find file: " + glomFile.getAbsolutePath();
134                                 } else {
135                                         message = "An unknown error occurred when trying to load file: " + glomFile.getAbsolutePath();
136                                 }
137                                 Log.error(message);
138                                 // continue with for loop because there may be other documents in the directory
139                                 continue;
140                         }
141
142                         ConfiguredDocument configuredDocument;
143                         try {
144                                 configuredDocument = new ConfiguredDocument(document);
145                         } catch (PropertyVetoException e) {
146                                 throw new ServletException(e.getMessage(), e);
147                         }
148                         // check if a username and password have been set and work for the current document
149                         String filename = glomFile.getName();
150                         String key = config.getKey(filename);
151                         if (key != null) {
152                                 String[] keyArray = key.split("\\.");
153                                 if (keyArray.length == 3 && "filename".equals(keyArray[2])) {
154                                         // username/password could be set, let's check to see if it works
155                                         String usernameKey = key.replaceAll(keyArray[2], "username");
156                                         String passwordKey = key.replaceAll(keyArray[2], "password");
157                                         try {
158                                                 configuredDocument.setUsernameAndPassword(config.getProperty(usernameKey).trim(),
159                                                                 config.getProperty(passwordKey));
160                                         } catch (SQLException e) {
161                                                 throw new ServletException(e.getMessage(), e);
162                                         }
163                                 }
164                         }
165
166                         // check the if the global username and password have been set and work with this document
167                         if (!configuredDocument.isAuthenticated()) {
168                                 try {
169                                         configuredDocument.setUsernameAndPassword(config.getProperty("glom.document.username").trim(),
170                                                         config.getProperty("glom.document.password"));
171                                 } catch (SQLException e) {
172                                         throw new ServletException(e.getMessage(), e);
173                                 }
174                         }
175
176                         // The key for the hash table is the file name without the .glom extension and with spaces ( ) replaced with
177                         // pluses (+). The space/plus replacement makes the key more friendly for URLs.
178                         String documentID = filename.substring(0, glomFile.getName().length() - GLOM_FILE_EXTENSION.length())
179                                         .replace(' ', '+');
180                         configuredDocument.setDocumentID(documentID);
181                         documentMapping.put(documentID, configuredDocument);
182                 }
183
184                 // Allow a fake connection, so sqlbuilder_get_full_query() can work:
185                 Glom.set_fake_connection();
186         }
187
188         /*
189          * This is called when the servlet is stopped or restarted.
190          * 
191          * @see javax.servlet.GenericServlet#destroy()
192          */
193         @Override
194         public void destroy() {
195                 Glom.libglom_deinit();
196
197                 for (String documenTitle : documentMapping.keySet()) {
198                         ConfiguredDocument configuredDoc = documentMapping.get(documenTitle);
199                         try {
200                                 DataSources.destroy(configuredDoc.getCpds());
201                         } catch (SQLException e) {
202                                 Log.error(documenTitle, "Error cleaning up the ComboPooledDataSource.", e);
203                         }
204                 }
205         }
206
207         /*
208          * (non-Javadoc)
209          * 
210          * @see org.glom.web.client.OnlineGlomService#getDocumentInfo(java.lang.String)
211          */
212         @Override
213         public DocumentInfo getDocumentInfo(String documentID) {
214
215                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
216
217                 // FIXME check for authentication
218
219                 return configuredDoc.getDocumentInfo();
220
221         }
222
223         /*
224          * (non-Javadoc)
225          * 
226          * @see org.glom.web.client.OnlineGlomService#getListViewLayout(java.lang.String, java.lang.String)
227          */
228         @Override
229         public LayoutGroup getListViewLayout(String documentID, String tableName) {
230                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
231
232                 // FIXME check for authentication
233
234                 return configuredDoc.getListViewLayoutGroup(tableName);
235         }
236
237         /*
238          * (non-Javadoc)
239          * 
240          * @see org.glom.web.client.OnlineGlomService#getListViewData(java.lang.String, java.lang.String, int, int)
241          */
242         @Override
243         public ArrayList<DataItem[]> getListViewData(String documentID, String tableName, int start, int length) {
244                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
245                 if (!configuredDoc.isAuthenticated()) {
246                         return new ArrayList<DataItem[]>();
247                 }
248                 return configuredDoc.getListViewData(tableName, start, length, false, 0, false);
249         }
250
251         /*
252          * (non-Javadoc)
253          * 
254          * @see org.glom.web.client.OnlineGlomService#getSortedListViewData(java.lang.String, java.lang.String, int, int,
255          * int, boolean)
256          */
257         @Override
258         public ArrayList<DataItem[]> getSortedListViewData(String documentID, String tableName, int start, int length,
259                         int sortColumnIndex, boolean isAscending) {
260                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
261                 if (!configuredDoc.isAuthenticated()) {
262                         return new ArrayList<DataItem[]>();
263                 }
264                 return configuredDoc.getListViewData(tableName, start, length, true, sortColumnIndex, isAscending);
265         }
266
267         /*
268          * (non-Javadoc)
269          * 
270          * @see org.glom.web.client.OnlineGlomService#getDocuments()
271          */
272         @Override
273         public Documents getDocuments() {
274                 Documents documents = new Documents();
275                 for (String documentID : documentMapping.keySet()) {
276                         ConfiguredDocument configuredDoc = documentMapping.get(documentID);
277                         documents.addDocument(documentID, configuredDoc.getDocument().get_database_title());
278                 }
279                 return documents;
280         }
281
282         /*
283          * (non-Javadoc)
284          * 
285          * @see org.glom.web.client.OnlineGlomService#isAuthenticated(java.lang.String)
286          */
287         public boolean isAuthenticated(String documentID) {
288                 return documentMapping.get(documentID).isAuthenticated();
289         }
290
291         /*
292          * (non-Javadoc)
293          * 
294          * @see org.glom.web.client.OnlineGlomService#checkAuthentication(java.lang.String, java.lang.String,
295          * java.lang.String)
296          */
297         @Override
298         public boolean checkAuthentication(String documentID, String username, String password) {
299                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
300                 try {
301                         return configuredDoc.setUsernameAndPassword(username, password);
302                 } catch (SQLException e) {
303                         Log.error(documentID, "Unknown SQL Error checking the database authentication.", e);
304                         return false;
305                 }
306         }
307
308         /*
309          * (non-Javadoc)
310          * 
311          * @see org.glom.web.client.OnlineGlomService#getDetailsData(java.lang.String, java.lang.String, java.lang.String)
312          */
313         @Override
314         public DataItem[] getDetailsData(String documentID, String tableName, PrimaryKeyItem primaryKeyValue) {
315                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
316
317                 // FIXME check for authentication
318
319                 return configuredDoc.getDetailsData(tableName, primaryKeyValue);
320         }
321
322         /*
323          * (non-Javadoc)
324          * 
325          * @see org.glom.web.client.OnlineGlomService#getDetailsLayoutAndData(java.lang.String, java.lang.String,
326          * java.lang.String)
327          */
328         @Override
329         public DetailsLayoutAndData getDetailsLayoutAndData(String documentID, String tableName,
330                         PrimaryKeyItem primaryKeyValue) {
331                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
332                 if (configuredDoc == null)
333                         return null;
334
335                 // FIXME check for authentication
336
337                 DetailsLayoutAndData initalDetailsView = new DetailsLayoutAndData();
338                 initalDetailsView.setLayout(configuredDoc.getDetailsLayoutGroup(tableName));
339                 initalDetailsView.setData(configuredDoc.getDetailsData(tableName, primaryKeyValue));
340
341                 return initalDetailsView;
342         }
343
344         /*
345          * (non-Javadoc)
346          * 
347          * @see org.glom.web.client.OnlineGlomService#getRelatedListData(java.lang.String, java.lang.String, int, int)
348          */
349         @Override
350         public ArrayList<DataItem[]> getRelatedListData(String documentID, String tableName, String relationshipName,
351                         PrimaryKeyItem foreignKeyValue, int start, int length) {
352                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
353
354                 // FIXME check for authentication
355
356                 return configuredDoc.getRelatedListData(tableName, relationshipName, foreignKeyValue, start, length, false, 0,
357                                 false);
358         }
359
360         /*
361          * (non-Javadoc)
362          * 
363          * @see org.glom.web.client.OnlineGlomService#getSortedRelatedListData(java.lang.String, java.lang.String, int, int,
364          * int, boolean)
365          */
366         @Override
367         public ArrayList<DataItem[]> getSortedRelatedListData(String documentID, String tableName, String relationshipName,
368                         PrimaryKeyItem foreignKeyValue, int start, int length, int sortColumnIndex, boolean ascending) {
369                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
370
371                 // FIXME check for authentication
372
373                 return configuredDoc.getRelatedListData(tableName, relationshipName, foreignKeyValue, start, length, true,
374                                 sortColumnIndex, ascending);
375         }
376
377         public int getRelatedListRowCount(String documentID, String tableName, String relationshipName,
378                         PrimaryKeyItem foreignKeyValue) {
379                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
380
381                 // FIXME check for authentication
382
383                 return configuredDoc.getRelatedListRowCount(tableName, relationshipName, foreignKeyValue);
384         }
385
386         /*
387          * (non-Javadoc)
388          * 
389          * @see org.glom.web.client.OnlineGlomService#getSuitableRecordToViewDetails(java.lang.String, java.lang.String,
390          * java.lang.String, java.lang.String)
391          */
392         @Override
393         public NavigationRecord getSuitableRecordToViewDetails(String documentID, String tableName,
394                         String relationshipName, PrimaryKeyItem primaryKeyValue) {
395                 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
396
397                 // FIXME check for authentication
398
399                 return configuredDoc.getSuitableRecordToViewDetails(tableName, relationshipName, primaryKeyValue);
400         }
401 }