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.InputStream;
24 import java.sql.Connection;
25 import java.sql.SQLException;
26 import java.util.ArrayList;
27 import java.util.Hashtable;
28 import java.util.List;
29 import java.util.Properties;
31 import javax.servlet.ServletException;
33 import org.apache.commons.io.FileUtils;
34 import org.apache.commons.io.FilenameUtils;
35 import org.apache.commons.lang3.StringUtils;
36 import org.glom.web.client.OnlineGlomService;
37 import org.glom.web.server.libglom.Document;
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.Reports;
44 import org.glom.web.shared.TypedDataItem;
45 import org.glom.web.shared.libglom.Report;
46 import org.glom.web.shared.libglom.layout.LayoutGroup;
47 import org.glom.web.shared.libglom.layout.LayoutItemPortal;
49 import com.google.gwt.user.server.rpc.RemoteServiceServlet;
50 import com.mchange.v2.c3p0.DataSources;
53 * This is the servlet class for setting up the server side of Online Glom. The client side can call the public methods
54 * in this class via OnlineGlom
56 * For instance, it loads all the available documents and provide a list - see getDocuments(). It then provides
57 * information from each document. For instance, see getListViewLayout().
59 * TODO: Watch for changes to the .glom files, to reload new versions and to load newly-added files. TODO: Watch for
60 * changes to the properties (configuration)?
62 @SuppressWarnings("serial")
63 public class OnlineGlomServiceImpl extends RemoteServiceServlet implements OnlineGlomService {
65 // convenience class for dealing with the Online Glom configuration file
67 private static class OnlineGlomProperties extends Properties {
69 /** Get the key for any *.*.filename = thefilename line.
74 private String getKey(final String filename) {
76 for (final String key : stringPropertyNames()) {
78 //Split the line at the . separators,
79 final String[] keyArray = key.split("\\.");
80 if (keyArray.length != 3)
82 if(!("filename".equals(keyArray[2]))) {
86 if (getProperty(key).trim().equals(filename)) {
94 public static class Credentials {
95 public String userName = "";
96 public String password = "";
99 public Credentials getCredentials(final String filename) {
100 Credentials result = null;
102 final String key = getKey(filename);
107 //Split the line at the . separators,
108 final String[] keyArray = key.split("\\.");
110 //Check that the third item is "filename", as expected:
111 if (keyArray.length == 3 && "filename".equals(keyArray[2])) {
112 result = new Credentials();
114 //Get the username and password for this file:
115 final String usernameKey = key.replaceAll(keyArray[2], "username");
116 final String passwordKey = key.replaceAll(keyArray[2], "password");
117 result.userName = getProperty(usernameKey).trim();
118 result.password = getProperty(passwordKey);
124 public String getGlobalUsername() {
125 return getProperty("glom.document.username").trim();
128 public String getGlobalPassword() {
129 return getProperty("glom.document.password");
132 public String getGlobalLocale() {
133 return getProperty("glom.document.locale");
136 public String getDocumentsDirectory() {
137 return getProperty("glom.document.directory");
141 private static final String GLOM_FILE_EXTENSION = "glom";
143 private final Hashtable<String, ConfiguredDocument> documentMapping = new Hashtable<String, ConfiguredDocument>();
144 private Exception configurationException = null;
149 * @see org.glom.web.client.OnlineGlomService#checkAuthentication(java.lang.String, java.lang.String,
153 public boolean checkAuthentication(final String documentID, final String username, final String password) {
154 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
155 if (configuredDoc == null) {
156 Log.error(documentID, "The document could not be found for this ID: " + documentID);
161 return configuredDoc.setUsernameAndPassword(username, password);
162 } catch (final SQLException e) {
163 Log.error(documentID, "Unknown SQL Error checking the database authentication.", e);
169 * This is called when the servlet is stopped or restarted.
171 * @see javax.servlet.GenericServlet#destroy()
174 public void destroy() {
175 for (final String documenTitle : documentMapping.keySet()) {
176 final ConfiguredDocument configuredDoc = documentMapping.get(documenTitle);
177 if (configuredDoc == null) {
182 DataSources.destroy(configuredDoc.getCpds());
183 } catch (final SQLException e) {
184 Log.error(documenTitle, "Error cleaning up the ComboPooledDataSource.", e);
192 * @see org.glom.web.client.OnlineGlomService#getConfigurationErrorMessage()
195 public String getConfigurationErrorMessage() {
196 if (configurationException == null) {
197 return "No configuration errors to report.";
198 } else if (configurationException.getMessage() == null) {
199 return configurationException.toString();
201 return configurationException.getMessage();
208 * @see org.glom.web.client.OnlineGlomService#getDetailsData(java.lang.String, java.lang.String, java.lang.String)
211 public DataItem[] getDetailsData(final String documentID, final String tableName,
212 final TypedDataItem primaryKeyValue) {
213 // An empty tableName is OK, because that means the default table.
215 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
216 if (configuredDoc == null) {
217 return new DataItem[0];
220 // FIXME check for authentication
222 return configuredDoc.getDetailsData(tableName, primaryKeyValue);
228 * @see org.glom.web.client.OnlineGlomService#getDetailsLayoutAndData(java.lang.String, java.lang.String,
232 public DetailsLayoutAndData getDetailsLayoutAndData(final String documentID, final String tableName,
233 final TypedDataItem primaryKeyValue, final String localeID) {
234 // An empty tableName is OK, because that means the default table.
236 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
237 if (configuredDoc == null) {
241 // FIXME check for authentication
243 final DetailsLayoutAndData initalDetailsView = new DetailsLayoutAndData();
245 .setLayout(configuredDoc.getDetailsLayoutGroup(tableName, StringUtils.defaultString(localeID)));
246 initalDetailsView.setData(configuredDoc.getDetailsData(tableName, primaryKeyValue));
248 return initalDetailsView;
254 * @see org.glom.web.client.OnlineGlomService#getDocumentInfo(java.lang.String)
257 public DocumentInfo getDocumentInfo(final String documentID, final String localeID) {
259 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
261 // Avoid dereferencing a null object:
262 if (configuredDoc == null) {
263 return new DocumentInfo();
266 // FIXME check for authentication
268 return configuredDoc.getDocumentInfo(StringUtils.defaultString(localeID));
275 * @see org.glom.web.client.OnlineGlomService#getDocuments()
278 public Documents getDocuments() {
279 final Documents documents = new Documents();
280 for (final String documentID : documentMapping.keySet()) {
281 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
282 if (configuredDoc == null) {
286 final Document glomDocument = configuredDoc.getDocument();
287 if (glomDocument == null) {
288 final String errorMessage = "getDocuments(): getDocument() failed.";
289 Log.fatal(errorMessage);
290 // TODO: throw new Exception(errorMessage);
294 final String localeID = StringUtils.defaultString(configuredDoc.getDefaultLocaleID());
295 documents.addDocument(documentID, glomDocument.getDatabaseTitle(localeID), localeID);
303 * @see org.glom.web.client.OnlineGlomService#getListViewData(java.lang.String, java.lang.String, int, int, int,
307 public ArrayList<DataItem[]> getListViewData(final String documentID, final String tableName,
308 final String quickFind, final int start, final int length, final int sortColumnIndex,
309 final boolean isAscending) {
310 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
311 if (configuredDoc == null) {
312 return new ArrayList<DataItem[]>();
315 if (!configuredDoc.isAuthenticated()) {
316 return new ArrayList<DataItem[]>();
318 return configuredDoc.getListViewData(tableName, quickFind, start, length, true, sortColumnIndex, isAscending);
324 * @see org.glom.web.client.OnlineGlomService#getListViewLayout(java.lang.String, java.lang.String)
327 public LayoutGroup getListViewLayout(final String documentID, final String tableName, final String localeID) {
328 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
329 if (configuredDoc == null) {
330 return new LayoutGroup();
333 // FIXME check for authentication
335 return configuredDoc.getListViewLayoutGroup(tableName, StringUtils.defaultString(localeID));
341 * @see org.glom.web.client.OnlineGlomService#getRelatedListData(java.lang.String, java.lang.String, int, int, int,
345 public ArrayList<DataItem[]> getRelatedListData(final String documentID, final String tableName,
346 final LayoutItemPortal portal, final TypedDataItem foreignKeyValue, final int start, final int length,
347 final int sortColumnIndex, final boolean ascending) {
348 // An empty tableName is OK, because that means the default table.
350 if (portal == null) {
351 Log.error("getRelatedListData(): portal is null.");
355 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
356 if (configuredDoc == null) {
357 return new ArrayList<DataItem[]>();
360 // FIXME check for authentication
362 return configuredDoc.getRelatedListData(tableName, portal, foreignKeyValue, start, length, sortColumnIndex,
367 public int getRelatedListRowCount(final String documentID, final String tableName, final LayoutItemPortal portal,
368 final TypedDataItem foreignKeyValue) {
369 // An empty tableName is OK, because that means the default table.
371 if (portal == null) {
372 Log.error("getRelatedListRowCount(): portal is null");
376 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
377 if (configuredDoc == null) {
381 // FIXME check for authentication
383 return configuredDoc.getRelatedListRowCount(tableName, portal, foreignKeyValue);
386 // TODO: Specify the foundset (via a where clause) and maybe a default sort order.
390 * @see org.glom.web.client.OnlineGlomService#getReportLayout(java.lang.String, java.lang.String, java.lang.String,
394 public String getReportHTML(final String documentID, final String tableName, final String reportName,
395 final String quickFind, final String localeID) {
396 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
397 if (configuredDoc == null) {
401 final Document glomDocument = configuredDoc.getDocument();
402 if (glomDocument == null) {
403 final String errorMessage = "getReportHTML(): getDocument() failed.";
404 Log.fatal(errorMessage);
405 // TODO: throw new Exception(errorMessage);
409 // FIXME check for authentication
411 final Report report = glomDocument.getReport(tableName, reportName);
412 if (report == null) {
413 Log.info(documentID, tableName, "The report layout is not defined for this table:" + reportName);
417 Connection connection;
419 connection = configuredDoc.getCpds().getConnection();
420 } catch (final SQLException e2) {
421 // TODO Auto-generated catch block
422 e2.printStackTrace();
423 return "Connection Failed";
426 // TODO: Use quickFind
427 final ReportGenerator generator = new ReportGenerator(StringUtils.defaultString(localeID));
428 return generator.generateReport(glomDocument, tableName, report, connection, quickFind);
434 * @see org.glom.web.client.OnlineGlomService#getReportsList(java.lang.String, java.lang.String, java.lang.String)
437 public Reports getReportsList(final String documentID, final String tableName, final String localeID) {
438 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
439 return configuredDoc.getReports(tableName, localeID);
442 // TODO: It would be more efficient to get the extra related (or related related) column value along with the other
444 // instead of doing a separate SQL query to get it now for a specific row.
448 * @see org.glom.web.client.OnlineGlomService#getSuitableRecordToViewDetails(java.lang.String, java.lang.String,
449 * java.lang.String, java.lang.String)
452 public NavigationRecord getSuitableRecordToViewDetails(final String documentID, final String tableName,
453 final LayoutItemPortal portal, final TypedDataItem primaryKeyValue) {
454 // An empty tableName is OK, because that means the default table.
456 if (portal == null) {
457 Log.error("getSuitableRecordToViewDetails(): portal is null");
461 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
462 if (configuredDoc == null) {
466 // FIXME check for authentication
468 return configuredDoc.getSuitableRecordToViewDetails(tableName, portal, primaryKeyValue);
472 * This is called when the servlet is started or restarted.
476 * @see javax.servlet.GenericServlet#init()
479 public void init() throws ServletException {
481 // All of the initialisation code is surrounded by a try/catch block so that the servlet can be in an
482 // initialised state and the error message can be retrieved by the client code.
484 // Find the configuration file. See this thread for background info:
485 // http://stackoverflow.com/questions/2161054/where-to-place-properties-files-in-a-jsp-servlet-web-application
486 // FIXME move onlineglom.properties to the WEB-INF folder (option number 2 from the stackoverflow question)
487 final OnlineGlomProperties config = new OnlineGlomProperties();
488 final InputStream is = Thread.currentThread().getContextClassLoader()
489 .getResourceAsStream("onlineglom.properties");
491 final String errorMessage = "onlineglom.properties not found.";
492 Log.fatal(errorMessage);
493 throw new Exception(errorMessage);
495 config.load(is); // can throw an IOException
497 // check if we can read the configured glom file directory
498 final String documentDirName = config.getDocumentsDirectory();
499 final File documentDir = new File(documentDirName);
500 if (!documentDir.isDirectory()) {
501 final String errorMessage = documentDirName + " is not a directory.";
502 Log.fatal(errorMessage);
503 throw new Exception(errorMessage);
505 if (!documentDir.canRead()) {
506 final String errorMessage = "Can't read the files in directory " + documentDirName + " .";
507 Log.fatal(errorMessage);
508 throw new Exception(errorMessage);
511 // get and check the glom files in the specified directory
513 final String[] extensions = { GLOM_FILE_EXTENSION };
514 final List<File> glomFiles = (List<File>) FileUtils
515 .listFiles(documentDir, extensions, true /* recursive */);
517 // don't continue if there aren't any Glom files to configure
518 if (glomFiles.size() <= 0) {
519 final String errorMessage = "Unable to find any Glom documents in the configured directory "
521 + " . Check the onlineglom.properties file to ensure that 'glom.document.directory' is set to the correct directory.";
522 Log.error(errorMessage);
523 throw new Exception(errorMessage);
526 // Check for a specified default locale,
527 // for table titles, field titles, etc:
528 final String globalLocaleID = StringUtils.defaultString(config.getGlobalLocale());
530 for (final File glomFile : glomFiles) {
531 final Document document = new Document();
532 document.setFileURI("file://" + glomFile.getAbsolutePath());
533 final boolean retval = document.load();
534 if (retval == false) {
535 final String message = "An error occurred when trying to load file: " + glomFile.getAbsolutePath();
537 // continue with for loop because there may be other documents in the directory
541 final ConfiguredDocument configuredDocument = new ConfiguredDocument(document); // can throw a
542 // PropertyVetoException
544 final String globalUserName = config.getGlobalUsername();
545 final String globalPassword = config.getGlobalPassword();
547 // check if a username and password have been set and work for the current document
548 final String filename = glomFile.getName();
550 // Username/password could be set. Let's check to see if it works.
551 final OnlineGlomProperties.Credentials docCredentials = config.getCredentials(filename);
552 if(docCredentials != null) {
553 configuredDocument.setUsernameAndPassword(docCredentials.userName, docCredentials.password); // can throw an SQLException
556 // check the if the global username and password have been set and work with this document
557 if (!configuredDocument.isAuthenticated()) {
558 configuredDocument.setUsernameAndPassword(globalUserName, globalPassword); // can throw an SQLException
561 if (!StringUtils.isEmpty(globalLocaleID)) {
562 configuredDocument.setDefaultLocaleID(globalLocaleID.trim());
565 addDocument(configuredDocument, filename);
568 } catch (final Exception e) {
569 // Don't throw the Exception so that servlet will be initialised and the error message can be retrieved.
570 configurationException = e;
575 private void addDocument(final ConfiguredDocument configuredDocument, final String filename) {
576 // The key for the hash table is the file name without the .glom extension and with spaces ( ) replaced
577 // with pluses (+). The space/plus replacement makes the key more friendly for URLs.
578 final String documentID = FilenameUtils.removeExtension(filename).replace(' ', '+');
579 configuredDocument.setDocumentID(documentID);
581 documentMapping.put(documentID, configuredDocument);
587 * @see org.glom.web.client.OnlineGlomService#isAuthenticated(java.lang.String)
590 public boolean isAuthenticated(final String documentID) {
591 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
592 if (configuredDoc == null) {
596 return configuredDoc.isAuthenticated();