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