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.Properties;
30 import javax.servlet.ServletException;
32 import org.glom.libglom.BakeryDocument.LoadFailureCodes;
33 import org.glom.libglom.Document;
34 import org.glom.libglom.Glom;
35 import org.glom.web.client.OnlineGlomService;
36 import org.glom.web.shared.DataItem;
37 import org.glom.web.shared.DetailsLayoutAndData;
38 import org.glom.web.shared.DocumentInfo;
39 import org.glom.web.shared.Documents;
40 import org.glom.web.shared.NavigationRecord;
41 import org.glom.web.shared.TypedDataItem;
42 import org.glom.web.shared.layout.LayoutGroup;
44 import com.google.gwt.user.server.rpc.RemoteServiceServlet;
45 import com.mchange.v2.c3p0.DataSources;
48 * The servlet class for setting up the server side of Online Glom. The public methods in this class are the methods
49 * that can be called by the client side code.
51 * @author Ben Konrath <ben@bagu.org>
53 @SuppressWarnings("serial")
54 public class OnlineGlomServiceImpl extends RemoteServiceServlet implements OnlineGlomService {
56 private static final String GLOM_FILE_EXTENSION = ".glom";
58 // convenience class to for dealing with the Online Glom configuration file
59 private class OnlineGlomProperties extends Properties {
60 public String getKey(String value) {
61 for (String key : stringPropertyNames()) {
62 if (getProperty(key).trim().equals(value))
69 private final Hashtable<String, ConfiguredDocument> documentMapping = new Hashtable<String, ConfiguredDocument>();
70 private Exception configurtionException = null;
73 * This is called when the servlet is started or restarted.
77 * @see javax.servlet.GenericServlet#init()
80 public void init() throws ServletException {
82 // All of the initialisation code is surrounded by a try/catch block so that the servlet can be in an
83 // initialised state and the error message can be retrieved by the client code.
85 // Find the configuration file. See this thread for background info:
86 // http://stackoverflow.com/questions/2161054/where-to-place-properties-files-in-a-jsp-servlet-web-application
87 // FIXME move onlineglom.properties to the WEB-INF folder (option number 2 from the stackoverflow question)
88 OnlineGlomProperties config = new OnlineGlomProperties();
89 InputStream is = Thread.currentThread().getContextClassLoader()
90 .getResourceAsStream("onlineglom.properties");
92 String errorMessage = "onlineglom.properties not found.";
93 Log.fatal(errorMessage);
94 throw new Exception(errorMessage);
96 config.load(is); // can throw an IOException
98 // check if we can read the configured glom file directory
99 String documentDirName = config.getProperty("glom.document.directory");
100 File documentDir = new File(documentDirName);
101 if (!documentDir.isDirectory()) {
102 String errorMessage = documentDirName + " is not a directory.";
103 Log.fatal(errorMessage);
104 throw new Exception(errorMessage);
106 if (!documentDir.canRead()) {
107 String errorMessage = "Can't read the files in directory " + documentDirName + " .";
108 Log.fatal(errorMessage);
109 throw new Exception(errorMessage);
112 // get and check the glom files in the specified directory
113 File[] glomFiles = documentDir.listFiles(new FilenameFilter() {
115 public boolean accept(File dir, String name) {
116 return name.endsWith(GLOM_FILE_EXTENSION);
120 // don't continue if there aren't any Glom files to configure
121 if (glomFiles.length <= 0) {
122 String errorMessage = "Unable to find any Glom documents in the configured directory "
124 + " . Check the onlineglom.properties file to ensure that 'glom.document.directory' is set to the correct directory.";
125 Log.error(errorMessage);
126 throw new Exception(errorMessage);
129 // Check to see if the native library of java libglom is visible to the JVM
130 if (!isNativeLibraryVisibleToJVM()) {
131 String errorMessage = "The java-libglom shared library is not visible to the JVM."
132 + " Ensure that 'java.library.path' is set with the path to the java-libglom shared library.";
133 Log.error(errorMessage);
134 throw new Exception(errorMessage);
137 // This initialisation method can throw an UnsatisfiedLinkError if the java-libglom native library isn't
138 // available. But since we're checking for the error condition above, the UnsatisfiedLinkError will never be
142 // Allow a fake connection, so sqlbuilder_get_full_query() can work:
143 Glom.set_fake_connection();
145 for (File glomFile : glomFiles) {
146 Document document = new Document();
147 document.set_file_uri("file://" + glomFile.getAbsolutePath());
149 boolean retval = document.load(error);
150 if (retval == false) {
152 if (LoadFailureCodes.LOAD_FAILURE_CODE_NOT_FOUND == LoadFailureCodes.swigToEnum(error)) {
153 message = "Could not find file: " + glomFile.getAbsolutePath();
155 message = "An unknown error occurred when trying to load file: " + glomFile.getAbsolutePath();
158 // continue with for loop because there may be other documents in the directory
162 ConfiguredDocument configuredDocument = new ConfiguredDocument(document); // can throw a
163 // PropertyVetoException
165 // check if a username and password have been set and work for the current document
166 String filename = glomFile.getName();
167 String key = config.getKey(filename);
169 String[] keyArray = key.split("\\.");
170 if (keyArray.length == 3 && "filename".equals(keyArray[2])) {
171 // username/password could be set, let's check to see if it works
172 String usernameKey = key.replaceAll(keyArray[2], "username");
173 String passwordKey = key.replaceAll(keyArray[2], "password");
174 configuredDocument.setUsernameAndPassword(config.getProperty(usernameKey).trim(),
175 config.getProperty(passwordKey)); // can throw an SQLException
179 // check the if the global username and password have been set and work with this document
180 if (!configuredDocument.isAuthenticated()) {
181 configuredDocument.setUsernameAndPassword(config.getProperty("glom.document.username").trim(),
182 config.getProperty("glom.document.password")); // can throw an SQLException
185 // The key for the hash table is the file name without the .glom extension and with spaces ( ) replaced
186 // with pluses (+). The space/plus replacement makes the key more friendly for URLs.
187 String documentID = filename.substring(0, glomFile.getName().length() - GLOM_FILE_EXTENSION.length())
189 configuredDocument.setDocumentID(documentID);
190 documentMapping.put(documentID, configuredDocument);
193 } catch (Exception e) {
194 // Don't throw the Exception so that servlet will be initialised and the error message can be retrieved.
195 configurtionException = e;
201 * Checks if the java-libglom native library is visible to the JVM.
203 * @return true if the java-libglom native library is visible to the JVM, false if it's not
205 private boolean isNativeLibraryVisibleToJVM() {
206 String javaLibraryPath = System.getProperty("java.library.path");
208 // Go through all the library paths and check for the java_libglom .so.
209 for (String libDirName : javaLibraryPath.split(":")) {
210 File libDir = new File(libDirName);
212 if (!libDir.isDirectory())
214 if (!libDir.canRead())
217 File[] libs = libDir.listFiles(new FilenameFilter() {
219 public boolean accept(File dir, String name) {
220 return name.matches("libjava_libglom-[0-9]\\.[0-9].+\\.so");
224 // if at least one directory had the .so, we're done
233 * This is called when the servlet is stopped or restarted.
235 * @see javax.servlet.GenericServlet#destroy()
238 public void destroy() {
239 Glom.libglom_deinit();
241 for (String documenTitle : documentMapping.keySet()) {
242 ConfiguredDocument configuredDoc = documentMapping.get(documenTitle);
244 DataSources.destroy(configuredDoc.getCpds());
245 } catch (SQLException e) {
246 Log.error(documenTitle, "Error cleaning up the ComboPooledDataSource.", e);
254 * @see org.glom.web.client.OnlineGlomService#getConfigurationErrorMessage()
257 public String getConfigurationErrorMessage() {
258 if (configurtionException == null)
259 return "No configuration errors to report.";
261 return configurtionException.getMessage();
267 * @see org.glom.web.client.OnlineGlomService#getDocumentInfo(java.lang.String)
270 public DocumentInfo getDocumentInfo(String documentID) {
272 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
274 // FIXME check for authentication
276 return configuredDoc.getDocumentInfo();
283 * @see org.glom.web.client.OnlineGlomService#getListViewLayout(java.lang.String, java.lang.String)
286 public LayoutGroup getListViewLayout(String documentID, String tableName) {
287 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
289 // FIXME check for authentication
291 return configuredDoc.getListViewLayoutGroup(tableName);
297 * @see org.glom.web.client.OnlineGlomService#getListViewData(java.lang.String, java.lang.String, int, int)
300 public ArrayList<DataItem[]> getListViewData(String documentID, String tableName, int start, int length) {
301 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
302 if (!configuredDoc.isAuthenticated()) {
303 return new ArrayList<DataItem[]>();
305 return configuredDoc.getListViewData(tableName, start, length, false, 0, false);
311 * @see org.glom.web.client.OnlineGlomService#getSortedListViewData(java.lang.String, java.lang.String, int, int,
315 public ArrayList<DataItem[]> getSortedListViewData(String documentID, String tableName, int start, int length,
316 int sortColumnIndex, boolean isAscending) {
317 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
318 if (!configuredDoc.isAuthenticated()) {
319 return new ArrayList<DataItem[]>();
321 return configuredDoc.getListViewData(tableName, start, length, true, sortColumnIndex, isAscending);
327 * @see org.glom.web.client.OnlineGlomService#getDocuments()
330 public Documents getDocuments() {
331 Documents documents = new Documents();
332 for (String documentID : documentMapping.keySet()) {
333 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
334 documents.addDocument(documentID, configuredDoc.getDocument().get_database_title());
342 * @see org.glom.web.client.OnlineGlomService#isAuthenticated(java.lang.String)
344 public boolean isAuthenticated(String documentID) {
345 return documentMapping.get(documentID).isAuthenticated();
351 * @see org.glom.web.client.OnlineGlomService#checkAuthentication(java.lang.String, java.lang.String,
355 public boolean checkAuthentication(String documentID, String username, String password) {
356 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
358 return configuredDoc.setUsernameAndPassword(username, password);
359 } catch (SQLException e) {
360 Log.error(documentID, "Unknown SQL Error checking the database authentication.", e);
368 * @see org.glom.web.client.OnlineGlomService#getDetailsData(java.lang.String, java.lang.String, java.lang.String)
371 public DataItem[] getDetailsData(String documentID, String tableName, TypedDataItem primaryKeyValue) {
372 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
374 // FIXME check for authentication
376 return configuredDoc.getDetailsData(tableName, primaryKeyValue);
382 * @see org.glom.web.client.OnlineGlomService#getDetailsLayoutAndData(java.lang.String, java.lang.String,
386 public DetailsLayoutAndData getDetailsLayoutAndData(String documentID, String tableName,
387 TypedDataItem primaryKeyValue) {
388 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
389 if (configuredDoc == null)
392 // FIXME check for authentication
394 DetailsLayoutAndData initalDetailsView = new DetailsLayoutAndData();
395 initalDetailsView.setLayout(configuredDoc.getDetailsLayoutGroup(tableName));
396 initalDetailsView.setData(configuredDoc.getDetailsData(tableName, primaryKeyValue));
398 return initalDetailsView;
404 * @see org.glom.web.client.OnlineGlomService#getRelatedListData(java.lang.String, java.lang.String, int, int)
407 public ArrayList<DataItem[]> getRelatedListData(String documentID, String tableName, String relationshipName,
408 TypedDataItem foreignKeyValue, int start, int length) {
409 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
411 // FIXME check for authentication
413 return configuredDoc.getRelatedListData(tableName, relationshipName, foreignKeyValue, start, length, false, 0,
420 * @see org.glom.web.client.OnlineGlomService#getSortedRelatedListData(java.lang.String, java.lang.String, int, int,
424 public ArrayList<DataItem[]> getSortedRelatedListData(String documentID, String tableName, String relationshipName,
425 TypedDataItem foreignKeyValue, int start, int length, int sortColumnIndex, boolean ascending) {
426 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
428 // FIXME check for authentication
430 return configuredDoc.getRelatedListData(tableName, relationshipName, foreignKeyValue, start, length, true,
431 sortColumnIndex, ascending);
434 public int getRelatedListRowCount(String documentID, String tableName, String relationshipName,
435 TypedDataItem foreignKeyValue) {
436 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
438 // FIXME check for authentication
440 return configuredDoc.getRelatedListRowCount(tableName, relationshipName, foreignKeyValue);
446 * @see org.glom.web.client.OnlineGlomService#getSuitableRecordToViewDetails(java.lang.String, java.lang.String,
447 * java.lang.String, java.lang.String)
450 public NavigationRecord getSuitableRecordToViewDetails(String documentID, String tableName,
451 String relationshipName, TypedDataItem primaryKeyValue) {
452 ConfiguredDocument configuredDoc = documentMapping.get(documentID);
454 // FIXME check for authentication
456 return configuredDoc.getSuitableRecordToViewDetails(tableName, relationshipName, primaryKeyValue);