2 * Copyright (C) 2010, 2011 Openismus GmbH
4 * This file is part of GWT-Glom.
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.
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
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/>.
20 package org.glom.web.server;
23 import java.io.FilenameFilter;
24 import java.io.InputStream;
25 import java.sql.Connection;
26 import java.sql.SQLException;
27 import java.util.ArrayList;
28 import java.util.Hashtable;
29 import java.util.List;
30 import java.util.Properties;
32 import javax.servlet.ServletException;
34 import org.apache.commons.io.FileUtils;
35 import org.apache.commons.io.FilenameUtils;
36 import org.apache.commons.lang.StringUtils;
37 import org.glom.libglom.BakeryDocument.LoadFailureCodes;
38 import org.glom.libglom.Document;
39 import org.glom.libglom.Glom;
40 import org.glom.libglom.Report;
41 import org.glom.web.client.OnlineGlomService;
42 import org.glom.web.shared.DataItem;
43 import org.glom.web.shared.DetailsLayoutAndData;
44 import org.glom.web.shared.DocumentInfo;
45 import org.glom.web.shared.Documents;
46 import org.glom.web.shared.NavigationRecord;
47 import org.glom.web.shared.Reports;
48 import org.glom.web.shared.TypedDataItem;
49 import org.glom.web.shared.layout.LayoutGroup;
51 import com.google.gwt.user.server.rpc.RemoteServiceServlet;
52 import com.mchange.v2.c3p0.DataSources;
55 * This is the servlet class for setting up the server side of Online Glom. The client side can call the public methods
56 * in this class via OnlineGlom
58 * For instance, it loads all the available documents and provide a list - see getDocuments(). It then provides
59 * information from each document. For instance, see getListViewLayout().
61 * TODO: Watch for changes to the .glom files, to reload new versions and to load newly-added files. TODO: Watch for
62 * changes to the properties (configuration)?
64 @SuppressWarnings("serial")
65 public class OnlineGlomServiceImpl extends RemoteServiceServlet implements OnlineGlomService {
67 private static final String GLOM_FILE_EXTENSION = "glom";
69 // convenience class for dealing with the Online Glom configuration file
70 private class OnlineGlomProperties extends Properties {
71 public String getKey(final String value) {
72 for (final String key : stringPropertyNames()) {
73 if (getProperty(key).trim().equals(value))
80 private final Hashtable<String, ConfiguredDocument> documentMapping = new Hashtable<String, ConfiguredDocument>();
81 private Exception configurationException = null;
84 * This is called when the servlet is started or restarted.
88 * @see javax.servlet.GenericServlet#init()
91 public void init() throws ServletException {
93 // All of the initialisation code is surrounded by a try/catch block so that the servlet can be in an
94 // initialised state and the error message can be retrieved by the client code.
96 // Find the configuration file. See this thread for background info:
97 // http://stackoverflow.com/questions/2161054/where-to-place-properties-files-in-a-jsp-servlet-web-application
98 // FIXME move onlineglom.properties to the WEB-INF folder (option number 2 from the stackoverflow question)
99 final OnlineGlomProperties config = new OnlineGlomProperties();
100 final InputStream is = Thread.currentThread().getContextClassLoader()
101 .getResourceAsStream("onlineglom.properties");
103 final String errorMessage = "onlineglom.properties not found.";
104 Log.fatal(errorMessage);
105 throw new Exception(errorMessage);
107 config.load(is); // can throw an IOException
109 // check if we can read the configured glom file directory
110 final String documentDirName = config.getProperty("glom.document.directory");
111 final File documentDir = new File(documentDirName);
112 if (!documentDir.isDirectory()) {
113 final String errorMessage = documentDirName + " is not a directory.";
114 Log.fatal(errorMessage);
115 throw new Exception(errorMessage);
117 if (!documentDir.canRead()) {
118 final String errorMessage = "Can't read the files in directory " + documentDirName + " .";
119 Log.fatal(errorMessage);
120 throw new Exception(errorMessage);
123 // get and check the glom files in the specified directory
125 final String[] extensions = { GLOM_FILE_EXTENSION };
126 final List<File> glomFiles = (List<File>) FileUtils
127 .listFiles(documentDir, extensions, true /* recursive */);
129 // don't continue if there aren't any Glom files to configure
130 if (glomFiles.size() <= 0) {
131 final String errorMessage = "Unable to find any Glom documents in the configured directory "
133 + " . Check the onlineglom.properties file to ensure that 'glom.document.directory' is set to the correct directory.";
134 Log.error(errorMessage);
135 throw new Exception(errorMessage);
138 // Check to see if the native library of java libglom is visible to the JVM
139 if (!isNativeLibraryVisibleToJVM()) {
140 final String errorMessage = "The java-libglom shared library is not visible to the JVM."
141 + " Ensure that 'java.library.path' is set with the path to the java-libglom shared library."
142 + "\n: java.library.path: " + System.getProperty("java.library.path");
143 Log.error(errorMessage);
144 throw new Exception(errorMessage);
147 // Check for a specified default locale,
148 // for table titles, field titles, etc:
149 final String globalLocaleID = StringUtils.defaultString(config.getProperty("glom.document.locale"));
151 // This initialisation method can throw an UnsatisfiedLinkError if the java-libglom native library isn't
152 // available. But since we're checking for the error condition above, the UnsatisfiedLinkError will never be
156 // Allow a fake connection, so sqlbuilder_get_full_query() can work:
157 Glom.set_fake_connection();
159 for (final File glomFile : glomFiles) {
160 final Document document = new Document();
161 document.set_file_uri("file://" + glomFile.getAbsolutePath());
163 final boolean retval = document.load(error);
164 if (retval == false) {
166 if (LoadFailureCodes.LOAD_FAILURE_CODE_NOT_FOUND == LoadFailureCodes.swigToEnum(error)) {
167 message = "Could not find file: " + glomFile.getAbsolutePath();
169 message = "An unknown error occurred when trying to load file: " + glomFile.getAbsolutePath();
172 // continue with for loop because there may be other documents in the directory
176 final ConfiguredDocument configuredDocument = new ConfiguredDocument(document); // can throw a
177 // PropertyVetoException
179 // check if a username and password have been set and work for the current document
180 final String filename = glomFile.getName();
181 final String key = config.getKey(filename);
183 final String[] keyArray = key.split("\\.");
184 if (keyArray.length == 3 && "filename".equals(keyArray[2])) {
185 // username/password could be set, let's check to see if it works
186 final String usernameKey = key.replaceAll(keyArray[2], "username");
187 final String passwordKey = key.replaceAll(keyArray[2], "password");
188 configuredDocument.setUsernameAndPassword(config.getProperty(usernameKey).trim(),
189 config.getProperty(passwordKey)); // can throw an SQLException
193 // check the if the global username and password have been set and work with this document
194 if (!configuredDocument.isAuthenticated()) {
195 configuredDocument.setUsernameAndPassword(config.getProperty("glom.document.username").trim(),
196 config.getProperty("glom.document.password")); // can throw an SQLException
199 if (!StringUtils.isEmpty(globalLocaleID)) {
200 configuredDocument.setDefaultLocaleID(globalLocaleID.trim());
203 // The key for the hash table is the file name without the .glom extension and with spaces ( ) replaced
204 // with pluses (+). The space/plus replacement makes the key more friendly for URLs.
205 final String documentID = FilenameUtils.removeExtension(filename).replace(' ', '+');
206 configuredDocument.setDocumentID(documentID);
207 documentMapping.put(documentID, configuredDocument);
210 } catch (final Exception e) {
211 // Don't throw the Exception so that servlet will be initialised and the error message can be retrieved.
212 configurationException = e;
218 * Checks if the java-libglom native library is visible to the JVM.
220 * @return true if the java-libglom native library is visible to the JVM, false if it's not
222 private boolean isNativeLibraryVisibleToJVM() {
223 final String javaLibraryPath = System.getProperty("java.library.path");
225 // Go through all the library paths and check for the java_libglom .so.
226 for (final String libDirName : javaLibraryPath.split(":")) {
227 final File libDir = new File(libDirName);
229 if (!libDir.isDirectory())
231 if (!libDir.canRead())
234 final File[] libs = libDir.listFiles(new FilenameFilter() {
236 public boolean accept(final File dir, final String name) {
237 return name.matches("libjava_libglom-[0-9]\\.[0-9].+\\.so");
241 // if at least one directory had the .so, we're done
250 * This is called when the servlet is stopped or restarted.
252 * @see javax.servlet.GenericServlet#destroy()
255 public void destroy() {
256 Glom.libglom_deinit();
258 for (final String documenTitle : documentMapping.keySet()) {
259 final ConfiguredDocument configuredDoc = documentMapping.get(documenTitle);
260 if (configuredDoc == null)
264 DataSources.destroy(configuredDoc.getCpds());
265 } catch (final SQLException e) {
266 Log.error(documenTitle, "Error cleaning up the ComboPooledDataSource.", e);
274 * @see org.glom.web.client.OnlineGlomService#getConfigurationErrorMessage()
277 public String getConfigurationErrorMessage() {
278 if (configurationException == null)
279 return "No configuration errors to report.";
280 else if (configurationException.getMessage() == null)
281 return configurationException.toString();
283 return configurationException.getMessage();
289 * @see org.glom.web.client.OnlineGlomService#getDocumentInfo(java.lang.String)
292 public DocumentInfo getDocumentInfo(final String documentID, final String localeID) {
294 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
296 // Avoid dereferencing a null object:
297 if (configuredDoc == null)
298 return new DocumentInfo();
300 // FIXME check for authentication
302 return configuredDoc.getDocumentInfo(StringUtils.defaultString(localeID));
309 * @see org.glom.web.client.OnlineGlomService#getListViewLayout(java.lang.String, java.lang.String)
312 public LayoutGroup getListViewLayout(final String documentID, final String tableName, final String localeID) {
313 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
314 if (configuredDoc == null)
315 return new LayoutGroup();
317 // FIXME check for authentication
319 return configuredDoc.getListViewLayoutGroup(tableName, StringUtils.defaultString(localeID));
322 // TODO: Specify the foundset (via a where clause) and maybe a default sort order.
326 * @see org.glom.web.client.OnlineGlomService#getReportLayout(java.lang.String, java.lang.String, java.lang.String,
330 public String getReportHTML(final String documentID, final String tableName, final String reportName, final String quickFind,
331 final String localeID) {
332 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
333 if (configuredDoc == null)
336 final Document glomDocument = configuredDoc.getDocument();
337 if (glomDocument == null) {
338 final String errorMessage = "getReportHTML(): getDocument() failed.";
339 Log.fatal(errorMessage);
340 // TODO: throw new Exception(errorMessage);
344 // FIXME check for authentication
346 final Report report = glomDocument.get_report(tableName, reportName);
347 if (report == null) {
348 Log.info(documentID, tableName, "The report layout is not defined for this table:" + reportName);
352 Connection connection;
354 connection = configuredDoc.getCpds().getConnection();
355 } catch (final SQLException e2) {
356 // TODO Auto-generated catch block
357 e2.printStackTrace();
358 return "Connection Failed";
361 // TODO: Use quickFind
362 final ReportGenerator generator = new ReportGenerator(StringUtils.defaultString(localeID));
363 return generator.generateReport(glomDocument, tableName, report, connection, quickFind);
369 * @see org.glom.web.client.OnlineGlomService#getListViewData(java.lang.String, java.lang.String, int, int)
372 public ArrayList<DataItem[]> getListViewData(final String documentID, final String tableName,
373 final String quickFind, final int start, final int length) {
374 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
375 if (!configuredDoc.isAuthenticated()) {
376 return new ArrayList<DataItem[]>();
378 return configuredDoc.getListViewData(tableName, quickFind, start, length, false, 0, false);
384 * @see org.glom.web.client.OnlineGlomService#getSortedListViewData(java.lang.String, java.lang.String, int, int,
388 public ArrayList<DataItem[]> getSortedListViewData(final String documentID, final String tableName,
389 final String quickFind, final int start, final int length, final int sortColumnIndex,
390 final boolean isAscending) {
391 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
392 if (configuredDoc == null)
393 return new ArrayList<DataItem[]>();
395 if (!configuredDoc.isAuthenticated()) {
396 return new ArrayList<DataItem[]>();
398 return configuredDoc.getListViewData(tableName, quickFind, start, length, true, sortColumnIndex, isAscending);
404 * @see org.glom.web.client.OnlineGlomService#getDocuments()
407 public Documents getDocuments() {
408 final Documents documents = new Documents();
409 for (final String documentID : documentMapping.keySet()) {
410 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
411 if (configuredDoc == null)
414 final Document glomDocument = configuredDoc.getDocument();
415 if (glomDocument == null) {
416 final String errorMessage = "getDocuments(): getDocument() failed.";
417 Log.fatal(errorMessage);
418 // TODO: throw new Exception(errorMessage);
422 final String localeID = StringUtils.defaultString(configuredDoc.getDefaultLocaleID());
423 documents.addDocument(documentID, glomDocument.get_database_title(localeID), localeID);
431 * @see org.glom.web.client.OnlineGlomService#isAuthenticated(java.lang.String)
434 public boolean isAuthenticated(final String documentID) {
435 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
436 if (configuredDoc == null)
439 return configuredDoc.isAuthenticated();
445 * @see org.glom.web.client.OnlineGlomService#checkAuthentication(java.lang.String, java.lang.String,
449 public boolean checkAuthentication(final String documentID, final String username, final String password) {
450 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
451 if (configuredDoc == null) {
452 Log.error(documentID, "The document could not be found for this ID: " + documentID);
457 return configuredDoc.setUsernameAndPassword(username, password);
458 } catch (final SQLException e) {
459 Log.error(documentID, "Unknown SQL Error checking the database authentication.", e);
467 * @see org.glom.web.client.OnlineGlomService#getReportsList(java.lang.String, java.lang.String, java.lang.String)
470 public Reports getReportsList(final String documentID, final String tableName, final String localeID) {
471 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
472 return configuredDoc.getReports(tableName, localeID);
478 * @see org.glom.web.client.OnlineGlomService#getDetailsData(java.lang.String, java.lang.String, java.lang.String)
481 public DataItem[] getDetailsData(final String documentID, final String tableName,
482 final TypedDataItem primaryKeyValue) {
483 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
484 if (configuredDoc == null)
485 return new DataItem[0];
487 // FIXME check for authentication
489 return configuredDoc.getDetailsData(tableName, primaryKeyValue);
495 * @see org.glom.web.client.OnlineGlomService#getDetailsLayoutAndData(java.lang.String, java.lang.String,
499 public DetailsLayoutAndData getDetailsLayoutAndData(final String documentID, final String tableName,
500 final TypedDataItem primaryKeyValue, final String localeID) {
501 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
502 if (configuredDoc == null)
505 // FIXME check for authentication
507 final DetailsLayoutAndData initalDetailsView = new DetailsLayoutAndData();
509 .setLayout(configuredDoc.getDetailsLayoutGroup(tableName, StringUtils.defaultString(localeID)));
510 initalDetailsView.setData(configuredDoc.getDetailsData(tableName, primaryKeyValue));
512 return initalDetailsView;
518 * @see org.glom.web.client.OnlineGlomService#getRelatedListData(java.lang.String, java.lang.String, int, int)
521 public ArrayList<DataItem[]> getRelatedListData(final String documentID, final String tableName,
522 final String relationshipName, final TypedDataItem foreignKeyValue, final int start, final int length) {
523 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
525 // FIXME check for authentication
527 return configuredDoc.getRelatedListData(tableName, relationshipName, foreignKeyValue, start, length, false, 0,
534 * @see org.glom.web.client.OnlineGlomService#getSortedRelatedListData(java.lang.String, java.lang.String, int, int,
538 public ArrayList<DataItem[]> getSortedRelatedListData(final String documentID, final String tableName,
539 final String relationshipName, final TypedDataItem foreignKeyValue, final int start, final int length,
540 final int sortColumnIndex, final boolean ascending) {
541 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
542 if (configuredDoc == null)
543 return new ArrayList<DataItem[]>();
545 // FIXME check for authentication
547 return configuredDoc.getRelatedListData(tableName, relationshipName, foreignKeyValue, start, length, true,
548 sortColumnIndex, ascending);
552 public int getRelatedListRowCount(final String documentID, final String tableName, final String relationshipName,
553 final TypedDataItem foreignKeyValue) {
554 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
555 if (configuredDoc == null)
558 // FIXME check for authentication
560 return configuredDoc.getRelatedListRowCount(tableName, relationshipName, foreignKeyValue);
566 * @see org.glom.web.client.OnlineGlomService#getSuitableRecordToViewDetails(java.lang.String, java.lang.String,
567 * java.lang.String, java.lang.String)
570 public NavigationRecord getSuitableRecordToViewDetails(final String documentID, final String tableName,
571 final String relationshipName, final TypedDataItem primaryKeyValue) {
572 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
573 if (configuredDoc == null)
574 return new NavigationRecord();
576 // FIXME check for authentication
578 return configuredDoc.getSuitableRecordToViewDetails(tableName, relationshipName, primaryKeyValue);