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;
48 import com.google.gwt.user.server.rpc.RemoteServiceServlet;
49 import com.mchange.v2.c3p0.DataSources;
52 * This is the servlet class for setting up the server side of Online Glom. The client side can call the public methods
53 * in this class via OnlineGlom
55 * For instance, it loads all the available documents and provide a list - see getDocuments(). It then provides
56 * information from each document. For instance, see getListViewLayout().
58 * TODO: Watch for changes to the .glom files, to reload new versions and to load newly-added files. TODO: Watch for
59 * changes to the properties (configuration)?
61 @SuppressWarnings("serial")
62 public class OnlineGlomServiceImpl extends RemoteServiceServlet implements OnlineGlomService {
64 private static final String GLOM_FILE_EXTENSION = "glom";
66 // convenience class for dealing with the Online Glom configuration file
67 // TODO: Turn this into a class with specific getters, and test it.
68 private static class OnlineGlomProperties extends Properties {
69 public String getKey(final String value) {
70 for (final String key : stringPropertyNames()) {
71 if (getProperty(key).trim().equals(value))
78 private final Hashtable<String, ConfiguredDocument> documentMapping = new Hashtable<String, ConfiguredDocument>();
79 private Exception configurationException = null;
82 * This is called when the servlet is started or restarted.
86 * @see javax.servlet.GenericServlet#init()
89 public void init() throws ServletException {
91 // All of the initialisation code is surrounded by a try/catch block so that the servlet can be in an
92 // initialised state and the error message can be retrieved by the client code.
94 // Find the configuration file. See this thread for background info:
95 // http://stackoverflow.com/questions/2161054/where-to-place-properties-files-in-a-jsp-servlet-web-application
96 // FIXME move onlineglom.properties to the WEB-INF folder (option number 2 from the stackoverflow question)
97 final OnlineGlomProperties config = new OnlineGlomProperties();
98 final InputStream is = Thread.currentThread().getContextClassLoader()
99 .getResourceAsStream("onlineglom.properties");
101 final String errorMessage = "onlineglom.properties not found.";
102 Log.fatal(errorMessage);
103 throw new Exception(errorMessage);
105 config.load(is); // can throw an IOException
107 // check if we can read the configured glom file directory
108 final String documentDirName = config.getProperty("glom.document.directory");
109 final File documentDir = new File(documentDirName);
110 if (!documentDir.isDirectory()) {
111 final String errorMessage = documentDirName + " is not a directory.";
112 Log.fatal(errorMessage);
113 throw new Exception(errorMessage);
115 if (!documentDir.canRead()) {
116 final String errorMessage = "Can't read the files in directory " + documentDirName + " .";
117 Log.fatal(errorMessage);
118 throw new Exception(errorMessage);
121 // get and check the glom files in the specified directory
123 final String[] extensions = { GLOM_FILE_EXTENSION };
124 final List<File> glomFiles = (List<File>) FileUtils
125 .listFiles(documentDir, extensions, true /* recursive */);
127 // don't continue if there aren't any Glom files to configure
128 if (glomFiles.size() <= 0) {
129 final String errorMessage = "Unable to find any Glom documents in the configured directory "
131 + " . Check the onlineglom.properties file to ensure that 'glom.document.directory' is set to the correct directory.";
132 Log.error(errorMessage);
133 throw new Exception(errorMessage);
136 // Check for a specified default locale,
137 // for table titles, field titles, etc:
138 final String globalLocaleID = StringUtils.defaultString(config.getProperty("glom.document.locale"));
140 for (final File glomFile : glomFiles) {
141 final Document document = new Document();
142 document.setFileURI("file://" + glomFile.getAbsolutePath());
143 final boolean retval = document.load();
144 if (retval == false) {
145 final String message = "An error occurred when trying to load file: " + glomFile.getAbsolutePath();
147 // continue with for loop because there may be other documents in the directory
151 final ConfiguredDocument configuredDocument = new ConfiguredDocument(document); // can throw a
152 // PropertyVetoException
154 // check if a username and password have been set and work for the current document
155 final String filename = glomFile.getName();
156 final String key = config.getKey(filename);
158 final String[] keyArray = key.split("\\.");
159 if (keyArray.length == 3 && "filename".equals(keyArray[2])) {
160 // username/password could be set, let's check to see if it works
161 final String usernameKey = key.replaceAll(keyArray[2], "username");
162 final String passwordKey = key.replaceAll(keyArray[2], "password");
163 configuredDocument.setUsernameAndPassword(config.getProperty(usernameKey).trim(),
164 config.getProperty(passwordKey)); // can throw an SQLException
168 // check the if the global username and password have been set and work with this document
169 if (!configuredDocument.isAuthenticated()) {
170 configuredDocument.setUsernameAndPassword(config.getProperty("glom.document.username").trim(),
171 config.getProperty("glom.document.password")); // can throw an SQLException
174 if (!StringUtils.isEmpty(globalLocaleID)) {
175 configuredDocument.setDefaultLocaleID(globalLocaleID.trim());
178 // The key for the hash table is the file name without the .glom extension and with spaces ( ) replaced
179 // with pluses (+). The space/plus replacement makes the key more friendly for URLs.
180 final String documentID = FilenameUtils.removeExtension(filename).replace(' ', '+');
181 configuredDocument.setDocumentID(documentID);
182 documentMapping.put(documentID, configuredDocument);
185 } catch (final Exception e) {
186 // Don't throw the Exception so that servlet will be initialised and the error message can be retrieved.
187 configurationException = e;
193 * This is called when the servlet is stopped or restarted.
195 * @see javax.servlet.GenericServlet#destroy()
198 public void destroy() {
199 for (final String documenTitle : documentMapping.keySet()) {
200 final ConfiguredDocument configuredDoc = documentMapping.get(documenTitle);
201 if (configuredDoc == null)
205 DataSources.destroy(configuredDoc.getCpds());
206 } catch (final SQLException e) {
207 Log.error(documenTitle, "Error cleaning up the ComboPooledDataSource.", e);
215 * @see org.glom.web.client.OnlineGlomService#getConfigurationErrorMessage()
218 public String getConfigurationErrorMessage() {
219 if (configurationException == null)
220 return "No configuration errors to report.";
221 else if (configurationException.getMessage() == null)
222 return configurationException.toString();
224 return configurationException.getMessage();
230 * @see org.glom.web.client.OnlineGlomService#getDocumentInfo(java.lang.String)
233 public DocumentInfo getDocumentInfo(final String documentID, final String localeID) {
235 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
237 // Avoid dereferencing a null object:
238 if (configuredDoc == null)
239 return new DocumentInfo();
241 // FIXME check for authentication
243 return configuredDoc.getDocumentInfo(StringUtils.defaultString(localeID));
250 * @see org.glom.web.client.OnlineGlomService#getListViewLayout(java.lang.String, java.lang.String)
253 public LayoutGroup getListViewLayout(final String documentID, final String tableName, final String localeID) {
254 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
255 if (configuredDoc == null)
256 return new LayoutGroup();
258 // FIXME check for authentication
260 return configuredDoc.getListViewLayoutGroup(tableName, StringUtils.defaultString(localeID));
263 // TODO: Specify the foundset (via a where clause) and maybe a default sort order.
267 * @see org.glom.web.client.OnlineGlomService#getReportLayout(java.lang.String, java.lang.String, java.lang.String,
271 public String getReportHTML(final String documentID, final String tableName, final String reportName,
272 final String quickFind, final String localeID) {
273 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
274 if (configuredDoc == null)
277 final Document glomDocument = configuredDoc.getDocument();
278 if (glomDocument == null) {
279 final String errorMessage = "getReportHTML(): getDocument() failed.";
280 Log.fatal(errorMessage);
281 // TODO: throw new Exception(errorMessage);
285 // FIXME check for authentication
287 final Report report = glomDocument.getReport(tableName, reportName);
288 if (report == null) {
289 Log.info(documentID, tableName, "The report layout is not defined for this table:" + reportName);
293 Connection connection;
295 connection = configuredDoc.getCpds().getConnection();
296 } catch (final SQLException e2) {
297 // TODO Auto-generated catch block
298 e2.printStackTrace();
299 return "Connection Failed";
302 // TODO: Use quickFind
303 final ReportGenerator generator = new ReportGenerator(StringUtils.defaultString(localeID));
304 return generator.generateReport(glomDocument, tableName, report, connection, quickFind);
310 * @see org.glom.web.client.OnlineGlomService#getListViewData(java.lang.String, java.lang.String, int, int)
313 public ArrayList<DataItem[]> getListViewData(final String documentID, final String tableName,
314 final String quickFind, final int start, final int length) {
315 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
316 if (!configuredDoc.isAuthenticated()) {
317 return new ArrayList<DataItem[]>();
319 return configuredDoc.getListViewData(tableName, quickFind, start, length, false, 0, false);
325 * @see org.glom.web.client.OnlineGlomService#getSortedListViewData(java.lang.String, java.lang.String, int, int,
329 public ArrayList<DataItem[]> getSortedListViewData(final String documentID, final String tableName,
330 final String quickFind, final int start, final int length, final int sortColumnIndex,
331 final boolean isAscending) {
332 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
333 if (configuredDoc == null)
334 return new ArrayList<DataItem[]>();
336 if (!configuredDoc.isAuthenticated()) {
337 return new ArrayList<DataItem[]>();
339 return configuredDoc.getListViewData(tableName, quickFind, start, length, true, sortColumnIndex, isAscending);
345 * @see org.glom.web.client.OnlineGlomService#getDocuments()
348 public Documents getDocuments() {
349 final Documents documents = new Documents();
350 for (final String documentID : documentMapping.keySet()) {
351 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
352 if (configuredDoc == null)
355 final Document glomDocument = configuredDoc.getDocument();
356 if (glomDocument == null) {
357 final String errorMessage = "getDocuments(): getDocument() failed.";
358 Log.fatal(errorMessage);
359 // TODO: throw new Exception(errorMessage);
363 final String localeID = StringUtils.defaultString(configuredDoc.getDefaultLocaleID());
364 documents.addDocument(documentID, glomDocument.getDatabaseTitle(localeID), localeID);
372 * @see org.glom.web.client.OnlineGlomService#isAuthenticated(java.lang.String)
375 public boolean isAuthenticated(final String documentID) {
376 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
377 if (configuredDoc == null)
380 return configuredDoc.isAuthenticated();
386 * @see org.glom.web.client.OnlineGlomService#checkAuthentication(java.lang.String, java.lang.String,
390 public boolean checkAuthentication(final String documentID, final String username, final String password) {
391 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
392 if (configuredDoc == null) {
393 Log.error(documentID, "The document could not be found for this ID: " + documentID);
398 return configuredDoc.setUsernameAndPassword(username, password);
399 } catch (final SQLException e) {
400 Log.error(documentID, "Unknown SQL Error checking the database authentication.", e);
408 * @see org.glom.web.client.OnlineGlomService#getReportsList(java.lang.String, java.lang.String, java.lang.String)
411 public Reports getReportsList(final String documentID, final String tableName, final String localeID) {
412 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
413 return configuredDoc.getReports(tableName, localeID);
419 * @see org.glom.web.client.OnlineGlomService#getDetailsData(java.lang.String, java.lang.String, java.lang.String)
422 public DataItem[] getDetailsData(final String documentID, final String tableName,
423 final TypedDataItem primaryKeyValue) {
424 //An empty tableName is OK, because that means the default table.
426 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
427 if (configuredDoc == null) {
428 return new DataItem[0];
431 // FIXME check for authentication
433 return configuredDoc.getDetailsData(tableName, primaryKeyValue);
439 * @see org.glom.web.client.OnlineGlomService#getDetailsLayoutAndData(java.lang.String, java.lang.String,
443 public DetailsLayoutAndData getDetailsLayoutAndData(final String documentID, final String tableName,
444 final TypedDataItem primaryKeyValue, final String localeID) {
445 //An empty tableName is OK, because that means the default table.
447 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
448 if (configuredDoc == null) {
452 // FIXME check for authentication
454 final DetailsLayoutAndData initalDetailsView = new DetailsLayoutAndData();
456 .setLayout(configuredDoc.getDetailsLayoutGroup(tableName, StringUtils.defaultString(localeID)));
457 initalDetailsView.setData(configuredDoc.getDetailsData(tableName, primaryKeyValue));
459 return initalDetailsView;
465 * @see org.glom.web.client.OnlineGlomService#getRelatedListData(java.lang.String, java.lang.String, int, int)
468 public ArrayList<DataItem[]> getRelatedListData(final String documentID, final String tableName,
469 final String relationshipName, final TypedDataItem foreignKeyValue, final int start, final int length) {
470 //An empty tableName is OK, because that means the default table.
472 if(StringUtils.isEmpty(relationshipName)) {
476 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
478 // FIXME check for authentication
480 return configuredDoc.getRelatedListData(tableName, relationshipName, foreignKeyValue, start, length, false, 0,
487 * @see org.glom.web.client.OnlineGlomService#getSortedRelatedListData(java.lang.String, java.lang.String, int, int,
491 public ArrayList<DataItem[]> getSortedRelatedListData(final String documentID, final String tableName,
492 final String relationshipName, final TypedDataItem foreignKeyValue, final int start, final int length,
493 final int sortColumnIndex, final boolean ascending) {
494 //An empty tableName is OK, because that means the default table.
496 if(StringUtils.isEmpty(relationshipName)) {
500 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
501 if (configuredDoc == null)
502 return new ArrayList<DataItem[]>();
504 // FIXME check for authentication
506 return configuredDoc.getRelatedListData(tableName, relationshipName, foreignKeyValue, start, length, true,
507 sortColumnIndex, ascending);
511 public int getRelatedListRowCount(final String documentID, final String tableName, final String relationshipName,
512 final TypedDataItem foreignKeyValue) {
513 //An empty tableName is OK, because that means the default table.
515 if(StringUtils.isEmpty(relationshipName)) {
519 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
520 if (configuredDoc == null) {
524 // FIXME check for authentication
526 return configuredDoc.getRelatedListRowCount(tableName, relationshipName, foreignKeyValue);
532 * @see org.glom.web.client.OnlineGlomService#getSuitableRecordToViewDetails(java.lang.String, java.lang.String,
533 * java.lang.String, java.lang.String)
536 public NavigationRecord getSuitableRecordToViewDetails(final String documentID, final String tableName,
537 final String relationshipName, final TypedDataItem primaryKeyValue) {
538 //An empty tableName is OK, because that means the default table.
540 if(StringUtils.isEmpty(relationshipName)) {
544 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
545 if (configuredDoc == null) {
549 // FIXME check for authentication
551 return configuredDoc.getSuitableRecordToViewDetails(tableName, relationshipName, primaryKeyValue);