2 * Copyright (C) 2013 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;
22 import java.io.BufferedReader;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.net.ServerSocket;
28 import java.sql.Connection;
29 import java.sql.DriverManager;
30 import java.sql.SQLException;
31 import java.text.NumberFormat;
32 import java.util.List;
33 import java.util.Locale;
35 import java.util.Properties;
36 import java.util.Map.Entry;
38 import org.glom.web.server.libglom.Document;
39 import org.glom.web.shared.DataItem;
40 import org.glom.web.shared.libglom.Field;
41 import org.jooq.InsertResultStep;
42 import org.jooq.InsertSetStep;
43 import org.jooq.Record;
44 import org.jooq.SQLDialect;
45 import org.jooq.Table;
46 import org.jooq.exception.DataAccessException;
47 import org.jooq.impl.Factory;
49 import com.google.common.io.Files;
52 * @author Murray Cumming <murrayc@openismus.com>
55 public class SelfHoster {
57 protected boolean selfHostingActive = false;
58 protected Document document = null;
59 protected String username = "";
60 protected String password = "";
65 public SelfHoster(final Document document) {
67 this.document = document;
70 public boolean createAndSelfHostFromExample() {
72 if (!createAndSelfHostNewEmpty()) {
73 // std::cerr << G_STRFUNC << ": test_create_and_selfhost_new_empty() failed." << std::endl;
77 final boolean recreated = recreateDatabaseFromDocument(); /* TODO: Progress callback */
90 protected boolean recreateDatabaseFromDocument() {
91 // TODO Auto-generated method stub
99 protected boolean createAndSelfHostNewEmpty() {
100 // TODO Auto-generated method stub
107 public boolean cleanup() {
108 //Derived classes should implement this.
112 public String getUsername() {
116 public String getPassword() {
123 protected boolean getSelfHostingActive() {
124 return selfHostingActive;
127 protected boolean executeCommandLineAndWait(final ProcessBuilder command) {
129 command.redirectErrorStream(true);
131 // Run the first command, and wait for it to return:
132 Process process = null;
134 process = command.start();
135 } catch (final IOException e) {
136 // TODO Auto-generated catch block
141 // final InputStream stderr = process.getInputStream();
142 // final InputStreamReader isr = new InputStreamReader(stderr);
143 // final BufferedReader br = new BufferedReader(isr);
144 // String output = "";
147 * try { //TODO: readLine() can hang, waiting for an end of line that never comes. while ((line = br.readLine())
148 * != null) { output += line + "\n"; } } catch (final IOException e1) { e1.printStackTrace(); return false; }
153 result = process.waitFor();
154 } catch (final InterruptedException e) {
155 // TODO Auto-generated catch block
161 System.out.println("executeCommandLineAndWait(): Command failed: " + command.command().toString());
162 InputStream is = process.getInputStream();
163 InputStreamReader isr = new InputStreamReader(is);
164 BufferedReader br = new BufferedReader(isr);
167 while ((line = br.readLine()) != null) {
168 System.out.println(line);
170 } catch (IOException e) {
171 // TODO Auto-generated catch block
181 protected boolean executeCommandLineAndWaitUntilSecondCommandReturnsSuccess(final ProcessBuilder command,
182 final ProcessBuilder commandSecond, final String secondCommandSuccessText) {
183 command.redirectErrorStream(true);
185 // Run the first command, and do not wait for it to return:
186 // Process process = null;
190 } catch (final IOException e) {
191 // TODO Auto-generated catch block
196 // final InputStream stderr = process.getInputStream();
197 // final InputStreamReader isr = new InputStreamReader(stderr);
198 // final BufferedReader br = new BufferedReader(isr);
201 * We do not wait, because this (postgres, for instance), does not return: final int result = process.waitFor();
202 * if (result != 0) { // TODO: Warn. return false; }
205 // Now run the second command, usually to verify that the first command has really done its work:
206 // We run this repeatedly until it succeeds, to show that the first command has finished.
207 boolean result = false;
209 result = executeCommandLineAndWait(commandSecond);
211 System.out.println("executeCommandLineAndWait(): second command succeeded.");
216 } catch (InterruptedException e) {
217 // TODO Auto-generated catch block
222 System.out.println("executeCommandLineAndWait(): Trying the second command again.");
226 // Try to get the output:
228 * if (!result) { String output = ""; /* String line; try { // TODO: readLine() can hang, waiting for an end of
229 * line that never comes. while ((line = br.readLine()) != null) { output += line + "\n";
230 * System.out.println(line); } } catch (final IOException e1) { // TODO Auto-generated catch block
231 * e1.printStackTrace(); return false; }
234 // System.out.println(" Output of first command: " + output);
235 // System.out.println(" first command: " + command.command().toString());
236 // System.out.println(" second command: " + commandSecond.command().toString());
245 protected boolean insertExampleData(final Connection connection, final Document document, final String tableName) {
247 final Factory factory = new Factory(connection, getSqlDialect());
248 final Table<Record> table = Factory.tableByName(tableName);
250 final List<Map<String, DataItem>> exampleRows = document.getExampleRows(tableName);
251 for (final Map<String, DataItem> row : exampleRows) {
252 InsertSetStep<Record> insertStep = factory.insertInto(table);
254 for (final Entry<String, DataItem> entry : row.entrySet()) {
255 final String fieldName = entry.getKey();
256 final DataItem value = entry.getValue();
261 final Field field = document.getField(tableName, fieldName);
266 final org.jooq.Field<Object> jooqField = Factory.fieldByName(field.getName());
267 if (jooqField == null) {
271 final Object fieldValue = value.getValue(field.getGlomType());
272 insertStep = insertStep.set(jooqField, fieldValue);
275 if (!(insertStep instanceof InsertResultStep<?>)) {
279 // We suppress the warning because we _do_ check the cast above.
280 @SuppressWarnings("unchecked")
281 final InsertResultStep<Record> insertResultStep = (InsertResultStep<Record>) insertStep;
284 insertResultStep.fetchOne();
285 } catch (final DataAccessException e) {
286 System.out.println("createAndSelfHostNewEmpty(): insertResultStep failed.");
290 // TODO: Check that it worked.
301 protected boolean addGroupsFromDocument(final Document document) {
302 // TODO Auto-generated method stub
310 protected boolean setTablePrivilegesGroupsFromDocument(final Document document) {
311 // TODO Auto-generated method stub
319 protected static boolean fileExists(final String filePath) {
320 final File file = new File(filePath);
321 return file.exists();
329 protected static int discoverFirstFreePort(final int start, final int end) {
330 for (int port = start; port <= end; ++port) {
332 final ServerSocket socket = new ServerSocket(port);
334 // If the instantiation succeeded then the port was free:
335 final int result = socket.getLocalPort(); // This must equal port.
338 } catch (final IOException ex) {
339 continue; // try next port
350 protected static boolean fileExistsAndIsExecutable(String path) {
351 final File file = new File(path);
352 if (!file.exists()) {
356 if (!file.canExecute()) {
367 protected String portNumberAsText(final int portNumber) {
368 final NumberFormat format = NumberFormat.getInstance(Locale.US);
369 format.setGroupingUsed(false); // TODO: Does this change it system-wide?
370 final String portAsText = format.format(portNumber);
376 public Connection createConnection(boolean failureExpected) {
377 //We don't just use SqlUtils.tryUsernameAndPassword() because it uses ComboPooledDataSource,
378 //which does not automatically close its connections,
379 //leading to errors because connections are already open.
380 final SqlUtils.JdbcConnectionDetails details = SqlUtils.getJdbcConnectionDetails(document);
381 if (details == null) {
385 final Properties connectionProps = new Properties();
386 connectionProps.put("user", this.username);
387 connectionProps.put("password", this.password);
389 Connection conn = null;
391 //TODO: Remove these debug prints when we figure out why getConnection sometimes hangs.
392 //System.out.println("debug: SelfHosterPostgreSQL.createConnection(): before createConnection()");
393 DriverManager.setLoginTimeout(10);
394 conn = DriverManager.getConnection(details.jdbcURL, connectionProps);
395 //System.out.println("debug: createConnection(): before createConnection()");
396 } catch (final SQLException e) {
397 if(!failureExpected) {
410 protected String shellQuote(final String str) {
411 // TODO: If we add the quotes then they seem to be used as part of the path, though that is not a problem with
412 // the normal command line.
416 // return "'" + str + "'";
420 * @return The temporary directory where the file was saved.
422 protected File saveDocumentCopy(Document.HostingMode hostingMode) {
423 // Save a copy, specifying the path to file in a directory:
424 // For instance, /tmp/testglom/testglom.glom");
425 final String tempFilename = "testglom";
426 final File tempFolder = Files.createTempDir();
427 final File tempDir = new File(tempFolder, tempFilename);
429 final String tempDirPath = tempDir.getPath();
430 final String tempFilePath = tempDirPath + File.separator + tempFilename;
431 final File file = new File(tempFilePath);
433 // Make sure that the file does not exist yet:
438 // Save the example as a real file:
439 document.setFileURI(file.getPath());
441 document.setHostingMode(hostingMode);
442 document.setIsExampleFile(false);
443 final boolean saved = document.save();
445 System.out.println("createAndSelfHostNewEmpty(): Document.save() failed.");
446 return null; // TODO: Delete the directory.
454 public SQLDialect getSqlDialect() {
455 //This must be overriden by the derived classes.
463 public static String quoteAndEscapeSqlId(final String name, final SQLDialect sqlDialect) {
464 //final Factory factory = new Factory(connection, getSqlDialect());
465 final org.jooq.Name jooqName = Factory.name(name);
466 if(jooqName == null) {
470 final Factory factory = new Factory(sqlDialect);
471 return factory.render(jooqName);