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;
30 import javax.servlet.ServletException;
32 import org.apache.commons.io.FileUtils;
33 import org.apache.commons.io.FilenameUtils;
34 import org.apache.commons.lang3.StringUtils;
35 import org.glom.web.client.OnlineGlomService;
36 import org.glom.web.server.OnlineGlomProperties;
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 private static final String GLOM_FILE_EXTENSION = "glom";
67 private final Hashtable<String, ConfiguredDocument> documentMapping = new Hashtable<String, ConfiguredDocument>();
68 private Exception configurationException = null;
73 * @see org.glom.web.client.OnlineGlomService#checkAuthentication(java.lang.String, java.lang.String,
77 public boolean checkAuthentication(final String documentID, final String username, final String password) {
78 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
79 if (configuredDoc == null) {
80 Log.error(documentID, "The document could not be found for this ID: " + documentID);
85 return configuredDoc.setUsernameAndPassword(username, password);
86 } catch (final SQLException e) {
87 Log.error(documentID, "Unknown SQL Error checking the database authentication.", e);
93 * This is called when the servlet is stopped or restarted.
95 * @see javax.servlet.GenericServlet#destroy()
98 public void destroy() {
99 for (final String documenTitle : documentMapping.keySet()) {
100 final ConfiguredDocument configuredDoc = documentMapping.get(documenTitle);
101 if (configuredDoc == null) {
106 DataSources.destroy(configuredDoc.getCpds());
107 } catch (final SQLException e) {
108 Log.error(documenTitle, "Error cleaning up the ComboPooledDataSource.", e);
116 * @see org.glom.web.client.OnlineGlomService#getConfigurationErrorMessage()
119 public String getConfigurationErrorMessage() {
120 if (configurationException == null) {
121 return "No configuration errors to report.";
122 } else if (configurationException.getMessage() == null) {
123 return configurationException.toString();
125 return configurationException.getMessage();
132 * @see org.glom.web.client.OnlineGlomService#getDetailsData(java.lang.String, java.lang.String, java.lang.String)
135 public DataItem[] getDetailsData(final String documentID, final String tableName,
136 final TypedDataItem primaryKeyValue) {
137 // An empty tableName is OK, because that means the default table.
139 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
140 if (configuredDoc == null) {
141 return new DataItem[0];
144 // FIXME check for authentication
146 return configuredDoc.getDetailsData(tableName, primaryKeyValue);
152 * @see org.glom.web.client.OnlineGlomService#getDetailsLayoutAndData(java.lang.String, java.lang.String,
156 public DetailsLayoutAndData getDetailsLayoutAndData(final String documentID, final String tableName,
157 final TypedDataItem primaryKeyValue, final String localeID) {
158 // An empty tableName is OK, because that means the default table.
160 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
161 if (configuredDoc == null) {
165 // FIXME check for authentication
167 final DetailsLayoutAndData initalDetailsView = new DetailsLayoutAndData();
169 .setLayout(configuredDoc.getDetailsLayoutGroup(tableName, StringUtils.defaultString(localeID)));
170 initalDetailsView.setData(configuredDoc.getDetailsData(tableName, primaryKeyValue));
172 return initalDetailsView;
178 * @see org.glom.web.client.OnlineGlomService#getDocumentInfo(java.lang.String)
181 public DocumentInfo getDocumentInfo(final String documentID, final String localeID) {
183 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
185 // Avoid dereferencing a null object:
186 if (configuredDoc == null) {
187 return new DocumentInfo();
190 // FIXME check for authentication
192 return configuredDoc.getDocumentInfo(StringUtils.defaultString(localeID));
199 * @see org.glom.web.client.OnlineGlomService#getDocuments()
202 public Documents getDocuments() {
203 final Documents documents = new Documents();
204 for (final String documentID : documentMapping.keySet()) {
205 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
206 if (configuredDoc == null) {
210 final Document glomDocument = configuredDoc.getDocument();
211 if (glomDocument == null) {
212 final String errorMessage = "getDocuments(): getDocument() failed.";
213 Log.fatal(errorMessage);
214 // TODO: throw new Exception(errorMessage);
218 final String localeID = StringUtils.defaultString(configuredDoc.getDefaultLocaleID());
219 documents.addDocument(documentID, glomDocument.getDatabaseTitle(localeID), localeID);
227 * @see org.glom.web.client.OnlineGlomService#getListViewData(java.lang.String, java.lang.String, int, int, int,
231 public ArrayList<DataItem[]> getListViewData(final String documentID, final String tableName,
232 final String quickFind, final int start, final int length, final int sortColumnIndex,
233 final boolean isAscending) {
234 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
235 if (configuredDoc == null) {
236 return new ArrayList<DataItem[]>();
239 if (!configuredDoc.isAuthenticated()) {
240 return new ArrayList<DataItem[]>();
242 return configuredDoc.getListViewData(tableName, quickFind, start, length, true, sortColumnIndex, isAscending);
248 * @see org.glom.web.client.OnlineGlomService#getListViewLayout(java.lang.String, java.lang.String)
251 public LayoutGroup getListViewLayout(final String documentID, final String tableName, final String localeID) {
252 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
253 if (configuredDoc == null) {
254 return new LayoutGroup();
257 // FIXME check for authentication
259 return configuredDoc.getListViewLayoutGroup(tableName, StringUtils.defaultString(localeID));
265 * @see org.glom.web.client.OnlineGlomService#getRelatedListData(java.lang.String, java.lang.String, int, int, int,
269 public ArrayList<DataItem[]> getRelatedListData(final String documentID, final String tableName,
270 final LayoutItemPortal portal, final TypedDataItem foreignKeyValue, final int start, final int length,
271 final int sortColumnIndex, final boolean ascending) {
272 // An empty tableName is OK, because that means the default table.
274 if (portal == null) {
275 Log.error("getRelatedListData(): portal is null.");
279 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
280 if (configuredDoc == null) {
281 return new ArrayList<DataItem[]>();
284 // FIXME check for authentication
286 return configuredDoc.getRelatedListData(tableName, portal, foreignKeyValue, start, length, sortColumnIndex,
291 public int getRelatedListRowCount(final String documentID, final String tableName, final LayoutItemPortal portal,
292 final TypedDataItem foreignKeyValue) {
293 // An empty tableName is OK, because that means the default table.
295 if (portal == null) {
296 Log.error("getRelatedListRowCount(): portal is null");
300 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
301 if (configuredDoc == null) {
305 // FIXME check for authentication
307 return configuredDoc.getRelatedListRowCount(tableName, portal, foreignKeyValue);
310 // TODO: Specify the foundset (via a where clause) and maybe a default sort order.
314 * @see org.glom.web.client.OnlineGlomService#getReportLayout(java.lang.String, java.lang.String, java.lang.String,
318 public String getReportHTML(final String documentID, final String tableName, final String reportName,
319 final String quickFind, final String localeID) {
320 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
321 if (configuredDoc == null) {
325 final Document glomDocument = configuredDoc.getDocument();
326 if (glomDocument == null) {
327 final String errorMessage = "getReportHTML(): getDocument() failed.";
328 Log.fatal(errorMessage);
329 // TODO: throw new Exception(errorMessage);
333 // FIXME check for authentication
335 final Report report = glomDocument.getReport(tableName, reportName);
336 if (report == null) {
337 Log.info(documentID, tableName, "The report layout is not defined for this table:" + reportName);
341 Connection connection;
343 connection = configuredDoc.getCpds().getConnection();
344 } catch (final SQLException e2) {
345 // TODO Auto-generated catch block
346 e2.printStackTrace();
347 return "Connection Failed";
350 // TODO: Use quickFind
351 final ReportGenerator generator = new ReportGenerator(StringUtils.defaultString(localeID));
352 return generator.generateReport(glomDocument, tableName, report, connection, quickFind);
358 * @see org.glom.web.client.OnlineGlomService#getReportsList(java.lang.String, java.lang.String, java.lang.String)
361 public Reports getReportsList(final String documentID, final String tableName, final String localeID) {
362 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
363 return configuredDoc.getReports(tableName, localeID);
366 // TODO: It would be more efficient to get the extra related (or related related) column value along with the other
368 // instead of doing a separate SQL query to get it now for a specific row.
372 * @see org.glom.web.client.OnlineGlomService#getSuitableRecordToViewDetails(java.lang.String, java.lang.String,
373 * java.lang.String, java.lang.String)
376 public NavigationRecord getSuitableRecordToViewDetails(final String documentID, final String tableName,
377 final LayoutItemPortal portal, final TypedDataItem primaryKeyValue) {
378 // An empty tableName is OK, because that means the default table.
380 if (portal == null) {
381 Log.error("getSuitableRecordToViewDetails(): portal is null");
385 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
386 if (configuredDoc == null) {
390 // FIXME check for authentication
392 return configuredDoc.getSuitableRecordToViewDetails(tableName, portal, primaryKeyValue);
396 * This is called when the servlet is started or restarted.
400 * @see javax.servlet.GenericServlet#init()
403 public void init() throws ServletException {
405 // All of the initialisation code is surrounded by a try/catch block so that the servlet can be in an
406 // initialised state and the error message can be retrieved by the client code.
408 // Find the configuration file. See this thread for background info:
409 // http://stackoverflow.com/questions/2161054/where-to-place-properties-files-in-a-jsp-servlet-web-application
410 // FIXME move onlineglom.properties to the WEB-INF folder (option number 2 from the stackoverflow question)
411 final OnlineGlomProperties config = new OnlineGlomProperties();
412 final InputStream is = Thread.currentThread().getContextClassLoader()
413 .getResourceAsStream("onlineglom.properties");
415 final String errorMessage = "onlineglom.properties not found.";
416 Log.fatal(errorMessage);
417 throw new Exception(errorMessage);
419 config.load(is); // can throw an IOException
421 // check if we can read the configured glom file directory
422 final String documentDirName = config.getDocumentsDirectory();
423 final File documentDir = new File(documentDirName);
424 if (!documentDir.isDirectory()) {
425 final String errorMessage = documentDirName + " is not a directory.";
426 Log.fatal(errorMessage);
427 throw new Exception(errorMessage);
429 if (!documentDir.canRead()) {
430 final String errorMessage = "Can't read the files in directory " + documentDirName + " .";
431 Log.fatal(errorMessage);
432 throw new Exception(errorMessage);
435 // get and check the glom files in the specified directory
437 final String[] extensions = { GLOM_FILE_EXTENSION };
438 final List<File> glomFiles = (List<File>) FileUtils
439 .listFiles(documentDir, extensions, true /* recursive */);
441 // don't continue if there aren't any Glom files to configure
442 if (glomFiles.size() <= 0) {
443 final String errorMessage = "Unable to find any Glom documents in the configured directory "
445 + " . Check the onlineglom.properties file to ensure that 'glom.document.directory' is set to the correct directory.";
446 Log.error(errorMessage);
447 throw new Exception(errorMessage);
450 // Check for a specified default locale,
451 // for table titles, field titles, etc:
452 final String globalLocaleID = StringUtils.defaultString(config.getGlobalLocale());
454 for (final File glomFile : glomFiles) {
455 final Document document = new Document();
456 document.setFileURI("file://" + glomFile.getAbsolutePath());
457 final boolean retval = document.load();
458 if (retval == false) {
459 final String message = "An error occurred when trying to load file: " + glomFile.getAbsolutePath();
461 // continue with for loop because there may be other documents in the directory
465 final ConfiguredDocument configuredDocument = new ConfiguredDocument(document); // can throw a
466 // PropertyVetoException
468 final String globalUserName = config.getGlobalUsername();
469 final String globalPassword = config.getGlobalPassword();
471 // check if a username and password have been set and work for the current document
472 final String filename = glomFile.getName();
474 // Username/password could be set. Let's check to see if it works.
475 final OnlineGlomProperties.Credentials docCredentials = config.getCredentials(filename);
476 if(docCredentials != null) {
477 configuredDocument.setUsernameAndPassword(docCredentials.userName, docCredentials.password); // can throw an SQLException
480 // check the if the global username and password have been set and work with this document
481 if (!configuredDocument.isAuthenticated()) {
482 configuredDocument.setUsernameAndPassword(globalUserName, globalPassword); // can throw an SQLException
485 if (!StringUtils.isEmpty(globalLocaleID)) {
486 configuredDocument.setDefaultLocaleID(globalLocaleID.trim());
489 addDocument(configuredDocument, filename);
492 } catch (final Exception e) {
493 // Don't throw the Exception so that servlet will be initialised and the error message can be retrieved.
494 configurationException = e;
499 private void addDocument(final ConfiguredDocument configuredDocument, final String filename) {
500 // The key for the hash table is the file name without the .glom extension and with spaces ( ) replaced
501 // with pluses (+). The space/plus replacement makes the key more friendly for URLs.
502 final String documentID = FilenameUtils.removeExtension(filename).replace(' ', '+');
503 configuredDocument.setDocumentID(documentID);
505 documentMapping.put(documentID, configuredDocument);
511 * @see org.glom.web.client.OnlineGlomService#isAuthenticated(java.lang.String)
514 public boolean isAuthenticated(final String documentID) {
515 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
516 if (configuredDoc == null) {
520 return configuredDoc.isAuthenticated();