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