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.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.glom.libglom.BakeryDocument.LoadFailureCodes;
36 import org.glom.libglom.Document;
37 import org.glom.libglom.Glom;
38 import org.glom.web.client.OnlineGlomService;
39 import org.glom.web.shared.DataItem;
40 import org.glom.web.shared.DetailsLayoutAndData;
41 import org.glom.web.shared.DocumentInfo;
42 import org.glom.web.shared.Documents;
43 import org.glom.web.shared.NavigationRecord;
44 import org.glom.web.shared.TypedDataItem;
45 import org.glom.web.shared.layout.LayoutGroup;
47 import com.google.gwt.user.server.rpc.RemoteServiceServlet;
48 import com.mchange.v2.c3p0.DataSources;
51 * This is the servlet class for setting up the server side of Online Glom. The client side can call the public methods
52 * in this class via OnlineGlom
54 * For instance, it loads all the available documents and provide a list - see getDocuments(). It then provides
55 * information from each document. For instance, see getListViewLayout().
57 * TODO: Watch for changes to the .glom files, to reload new versions and to load newly-added files. TODO: Watch for
58 * changes to the properties (configuration)?
60 @SuppressWarnings("serial")
61 public class OnlineGlomServiceImpl extends RemoteServiceServlet implements OnlineGlomService {
63 private static final String GLOM_FILE_EXTENSION = "glom";
65 // convenience class for dealing with the Online Glom configuration file
66 private class OnlineGlomProperties extends Properties {
67 public String getKey(final String value) {
68 for (final String key : stringPropertyNames()) {
69 if (getProperty(key).trim().equals(value))
76 private final Hashtable<String, ConfiguredDocument> documentMapping = new Hashtable<String, ConfiguredDocument>();
77 private Exception configurationException = null;
80 * This is called when the servlet is started or restarted.
84 * @see javax.servlet.GenericServlet#init()
87 public void init() throws ServletException {
89 // All of the initialisation code is surrounded by a try/catch block so that the servlet can be in an
90 // initialised state and the error message can be retrieved by the client code.
92 // Find the configuration file. See this thread for background info:
93 // http://stackoverflow.com/questions/2161054/where-to-place-properties-files-in-a-jsp-servlet-web-application
94 // FIXME move onlineglom.properties to the WEB-INF folder (option number 2 from the stackoverflow question)
95 final OnlineGlomProperties config = new OnlineGlomProperties();
96 final InputStream is = Thread.currentThread().getContextClassLoader()
97 .getResourceAsStream("onlineglom.properties");
99 final String errorMessage = "onlineglom.properties not found.";
100 Log.fatal(errorMessage);
101 throw new Exception(errorMessage);
103 config.load(is); // can throw an IOException
105 // check if we can read the configured glom file directory
106 final String documentDirName = config.getProperty("glom.document.directory");
107 final File documentDir = new File(documentDirName);
108 if (!documentDir.isDirectory()) {
109 final String errorMessage = documentDirName + " is not a directory.";
110 Log.fatal(errorMessage);
111 throw new Exception(errorMessage);
113 if (!documentDir.canRead()) {
114 final String errorMessage = "Can't read the files in directory " + documentDirName + " .";
115 Log.fatal(errorMessage);
116 throw new Exception(errorMessage);
119 // get and check the glom files in the specified directory
121 final String[] extensions = { GLOM_FILE_EXTENSION };
122 @SuppressWarnings("unchecked")
123 final List<File> glomFiles = (List<File>) FileUtils
124 .listFiles(documentDir, extensions, true /* recursive */);
126 // don't continue if there aren't any Glom files to configure
127 if (glomFiles.size() <= 0) {
128 final String errorMessage = "Unable to find any Glom documents in the configured directory "
130 + " . Check the onlineglom.properties file to ensure that 'glom.document.directory' is set to the correct directory.";
131 Log.error(errorMessage);
132 throw new Exception(errorMessage);
135 // Check to see if the native library of java libglom is visible to the JVM
136 if (!isNativeLibraryVisibleToJVM()) {
137 final String errorMessage = "The java-libglom shared library is not visible to the JVM."
138 + " Ensure that 'java.library.path' is set with the path to the java-libglom shared library.";
139 Log.error(errorMessage);
140 throw new Exception(errorMessage);
143 // Check for a specified default locale,
144 // for table titles, field titles, etc:
145 final String globalLocaleID = config.getProperty("glom.document.locale");
147 // This initialisation method can throw an UnsatisfiedLinkError if the java-libglom native library isn't
148 // available. But since we're checking for the error condition above, the UnsatisfiedLinkError will never be
152 // Allow a fake connection, so sqlbuilder_get_full_query() can work:
153 Glom.set_fake_connection();
155 for (final File glomFile : glomFiles) {
156 final Document document = new Document();
157 document.set_file_uri("file://" + glomFile.getAbsolutePath());
159 final boolean retval = document.load(error);
160 if (retval == false) {
162 if (LoadFailureCodes.LOAD_FAILURE_CODE_NOT_FOUND == LoadFailureCodes.swigToEnum(error)) {
163 message = "Could not find file: " + glomFile.getAbsolutePath();
165 message = "An unknown error occurred when trying to load file: " + glomFile.getAbsolutePath();
168 // continue with for loop because there may be other documents in the directory
172 final ConfiguredDocument configuredDocument = new ConfiguredDocument(document); // can throw a
173 // PropertyVetoException
175 // check if a username and password have been set and work for the current document
176 final String filename = glomFile.getName();
177 final String key = config.getKey(filename);
179 final String[] keyArray = key.split("\\.");
180 if (keyArray.length == 3 && "filename".equals(keyArray[2])) {
181 // username/password could be set, let's check to see if it works
182 final String usernameKey = key.replaceAll(keyArray[2], "username");
183 final String passwordKey = key.replaceAll(keyArray[2], "password");
184 configuredDocument.setUsernameAndPassword(config.getProperty(usernameKey).trim(),
185 config.getProperty(passwordKey)); // can throw an SQLException
189 // check the if the global username and password have been set and work with this document
190 if (!configuredDocument.isAuthenticated()) {
191 configuredDocument.setUsernameAndPassword(config.getProperty("glom.document.username").trim(),
192 config.getProperty("glom.document.password")); // can throw an SQLException
195 if (globalLocaleID != null && !globalLocaleID.isEmpty()) {
196 configuredDocument.setDefaultLocaleID(globalLocaleID.trim());
199 // The key for the hash table is the file name without the .glom extension and with spaces ( ) replaced
200 // with pluses (+). The space/plus replacement makes the key more friendly for URLs.
201 final String documentID = FilenameUtils.removeExtension(filename).replace(' ', '+');
202 configuredDocument.setDocumentID(documentID);
203 documentMapping.put(documentID, configuredDocument);
206 } catch (final Exception e) {
207 // Don't throw the Exception so that servlet will be initialised and the error message can be retrieved.
208 configurationException = e;
214 * Checks if the java-libglom native library is visible to the JVM.
216 * @return true if the java-libglom native library is visible to the JVM, false if it's not
218 private boolean isNativeLibraryVisibleToJVM() {
219 final String javaLibraryPath = System.getProperty("java.library.path");
221 // Go through all the library paths and check for the java_libglom .so.
222 for (final String libDirName : javaLibraryPath.split(":")) {
223 final File libDir = new File(libDirName);
225 if (!libDir.isDirectory())
227 if (!libDir.canRead())
230 final File[] libs = libDir.listFiles(new FilenameFilter() {
232 public boolean accept(final File dir, final String name) {
233 return name.matches("libjava_libglom-[0-9]\\.[0-9].+\\.so");
237 // if at least one directory had the .so, we're done
246 * This is called when the servlet is stopped or restarted.
248 * @see javax.servlet.GenericServlet#destroy()
251 public void destroy() {
252 Glom.libglom_deinit();
254 for (final String documenTitle : documentMapping.keySet()) {
255 final ConfiguredDocument configuredDoc = documentMapping.get(documenTitle);
257 DataSources.destroy(configuredDoc.getCpds());
258 } catch (final SQLException e) {
259 Log.error(documenTitle, "Error cleaning up the ComboPooledDataSource.", e);
267 * @see org.glom.web.client.OnlineGlomService#getConfigurationErrorMessage()
270 public String getConfigurationErrorMessage() {
271 if (configurationException == null)
272 return "No configuration errors to report.";
273 else if (configurationException.getMessage() == null)
274 return configurationException.toString();
276 return configurationException.getMessage();
282 * @see org.glom.web.client.OnlineGlomService#getDocumentInfo(java.lang.String)
285 public DocumentInfo getDocumentInfo(final String documentID, final String localeID) {
287 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
289 // FIXME check for authentication
291 return configuredDoc.getDocumentInfo(localeID);
298 * @see org.glom.web.client.OnlineGlomService#getListViewLayout(java.lang.String, java.lang.String)
301 public LayoutGroup getListViewLayout(final String documentID, final String tableName, final String localeID) {
302 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
304 // FIXME check for authentication
306 return configuredDoc.getListViewLayoutGroup(tableName, localeID);
312 * @see org.glom.web.client.OnlineGlomService#getListViewData(java.lang.String, java.lang.String, int, int)
315 public ArrayList<DataItem[]> getListViewData(final String documentID, final String tableName,
316 final String quickFind, final int start, final int length) {
317 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
318 if (!configuredDoc.isAuthenticated()) {
319 return new ArrayList<DataItem[]>();
321 return configuredDoc.getListViewData(tableName, quickFind, start, length, false, 0, false);
327 * @see org.glom.web.client.OnlineGlomService#getSortedListViewData(java.lang.String, java.lang.String, int, int,
331 public ArrayList<DataItem[]> getSortedListViewData(final String documentID, final String tableName,
332 final String quickFind, final int start, final int length, final int sortColumnIndex,
333 final boolean isAscending) {
334 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
335 if (!configuredDoc.isAuthenticated()) {
336 return new ArrayList<DataItem[]>();
338 return configuredDoc.getListViewData(tableName, quickFind, start, length, true, sortColumnIndex, isAscending);
344 * @see org.glom.web.client.OnlineGlomService#getDocuments()
347 public Documents getDocuments() {
348 final Documents documents = new Documents();
349 for (final String documentID : documentMapping.keySet()) {
350 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
351 documents.addDocument(documentID, configuredDoc.getDocument().get_database_title(),
352 configuredDoc.getDefaultLocaleID());
360 * @see org.glom.web.client.OnlineGlomService#isAuthenticated(java.lang.String)
363 public boolean isAuthenticated(final String documentID) {
364 return documentMapping.get(documentID).isAuthenticated();
370 * @see org.glom.web.client.OnlineGlomService#checkAuthentication(java.lang.String, java.lang.String,
374 public boolean checkAuthentication(final String documentID, final String username, final String password) {
375 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
377 return configuredDoc.setUsernameAndPassword(username, password);
378 } catch (final SQLException e) {
379 Log.error(documentID, "Unknown SQL Error checking the database authentication.", e);
387 * @see org.glom.web.client.OnlineGlomService#getDetailsData(java.lang.String, java.lang.String, java.lang.String)
390 public DataItem[] getDetailsData(final String documentID, final String tableName,
391 final TypedDataItem primaryKeyValue) {
392 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
394 // FIXME check for authentication
396 return configuredDoc.getDetailsData(tableName, primaryKeyValue);
402 * @see org.glom.web.client.OnlineGlomService#getDetailsLayoutAndData(java.lang.String, java.lang.String,
406 public DetailsLayoutAndData getDetailsLayoutAndData(final String documentID, final String tableName,
407 final TypedDataItem primaryKeyValue, final String localeID) {
408 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
409 if (configuredDoc == null)
412 // FIXME check for authentication
414 final DetailsLayoutAndData initalDetailsView = new DetailsLayoutAndData();
415 initalDetailsView.setLayout(configuredDoc.getDetailsLayoutGroup(tableName, localeID));
416 initalDetailsView.setData(configuredDoc.getDetailsData(tableName, primaryKeyValue));
418 return initalDetailsView;
424 * @see org.glom.web.client.OnlineGlomService#getRelatedListData(java.lang.String, java.lang.String, int, int)
427 public ArrayList<DataItem[]> getRelatedListData(final String documentID, final String tableName,
428 final String relationshipName, final TypedDataItem foreignKeyValue, final int start, final int length) {
429 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
431 // FIXME check for authentication
433 return configuredDoc.getRelatedListData(tableName, relationshipName, foreignKeyValue, start, length, false, 0,
440 * @see org.glom.web.client.OnlineGlomService#getSortedRelatedListData(java.lang.String, java.lang.String, int, int,
444 public ArrayList<DataItem[]> getSortedRelatedListData(final String documentID, final String tableName,
445 final String relationshipName, final TypedDataItem foreignKeyValue, final int start, final int length,
446 final int sortColumnIndex, final boolean ascending) {
447 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
449 // FIXME check for authentication
451 return configuredDoc.getRelatedListData(tableName, relationshipName, foreignKeyValue, start, length, true,
452 sortColumnIndex, ascending);
456 public int getRelatedListRowCount(final String documentID, final String tableName, final String relationshipName,
457 final TypedDataItem foreignKeyValue) {
458 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
460 // FIXME check for authentication
462 return configuredDoc.getRelatedListRowCount(tableName, relationshipName, foreignKeyValue);
468 * @see org.glom.web.client.OnlineGlomService#getSuitableRecordToViewDetails(java.lang.String, java.lang.String,
469 * java.lang.String, java.lang.String)
472 public NavigationRecord getSuitableRecordToViewDetails(final String documentID, final String tableName,
473 final String relationshipName, final TypedDataItem primaryKeyValue) {
474 final ConfiguredDocument configuredDoc = documentMapping.get(documentID);
476 // FIXME check for authentication
478 return configuredDoc.getSuitableRecordToViewDetails(tableName, relationshipName, primaryKeyValue);