2 * Copyright (C) 2012 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.FileNotFoundException;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.net.ServerSocket;
30 import java.sql.Connection;
31 import java.sql.DriverManager;
32 import java.sql.SQLException;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.Locale;
37 import java.util.Map.Entry;
38 import java.util.Properties;
40 import org.apache.commons.lang3.StringUtils;
41 import org.glom.web.server.libglom.Document;
42 import org.glom.web.shared.DataItem;
43 import org.glom.web.shared.libglom.Field;
44 import org.jooq.InsertResultStep;
45 import org.jooq.InsertSetStep;
46 import org.jooq.Record;
47 import org.jooq.SQLDialect;
48 import org.jooq.Table;
49 import org.jooq.exception.DataAccessException;
50 import org.jooq.impl.Factory;
52 import com.google.common.io.Files;
53 import com.ibm.icu.text.NumberFormat;
56 * @author Murray Cumming <murrayc@murrayc.com>
59 public class SelfHoster {
60 // private String tempFilepathDir = "";
61 private boolean selfHostingActive = false;
62 private Document document = null;
63 private String username = "";
64 private String password = "";
66 SelfHoster(final Document document) {
67 this.document = document;
70 private static final int PORT_POSTGRESQL_SELF_HOSTED_START = 5433;
71 private static final int PORT_POSTGRESQL_SELF_HOSTED_END = 5500;
73 private static final String DEFAULT_CONFIG_PG_HBA_LOCAL_8p4 = "# TYPE DATABASE USER CIDR-ADDRESS METHOD\n"
75 + "# local is for Unix domain socket connections only\n"
76 + "# trust allows connection from the current PC without a password:\n"
77 + "local all all trust\n"
78 + "local all all ident\n"
79 + "local all all md5\n"
81 + "# TCP connections from the same computer, with a password:\n"
82 + "host all all 127.0.0.1 255.255.255.255 md5\n"
83 + "# IPv6 local connections:\n"
84 + "host all all ::1/128 md5\n";
86 private static final String DEFAULT_CONFIG_PG_IDENT = "";
87 private static final String FILENAME_DATA = "data";
89 public boolean createAndSelfHostFromExample(final Document.HostingMode hostingMode) {
91 if (!createAndSelfHostNewEmpty(hostingMode)) {
92 // std::cerr << G_STRFUNC << ": test_create_and_selfhost_new_empty() failed." << std::endl;
96 final boolean recreated = recreateDatabaseFromDocument(); /* TODO: Progress callback */
107 * @param subDirectoryPath
110 private boolean createAndSelfHostNewEmpty(final Document.HostingMode hostingMode) {
111 if (hostingMode != Document.HostingMode.HOSTING_MODE_POSTGRES_SELF) {
112 // TODO: std::cerr << G_STRFUNC << ": This test function does not support the specified hosting_mode: " <<
113 // hosting_mode << std::endl;
117 // Save a copy, specifying the path to file in a directory:
118 // For instance, /tmp/testglom/testglom.glom");
119 final String tempFilename = "testglom";
120 final File tempFolder = Files.createTempDir();
121 final File tempDir = new File(tempFolder, tempFilename);
123 final String tempDirPath = tempDir.getPath();
124 final String tempFilePath = tempDirPath + File.separator + tempFilename;
125 final File file = new File(tempFilePath);
127 // Make sure that the file does not exist yet:
132 // Save the example as a real file:
133 document.setFileURI(file.getPath());
135 document.setHostingMode(hostingMode);
136 document.setIsExampleFile(false);
137 final boolean saved = document.save();
140 return false; // TODO: Delete the directory.
143 // We must specify a default username and password:
144 final String user = "glom_default_developer_user";
145 final String password = "glom_default_developer_password";
147 // Create the self-hosting files:
148 if (!initialize(user, password)) {
150 // TODO: Delete directory.
153 // Check that it really created some files:
154 if (!tempDir.exists()) {
156 // TODO: Delete directory.
159 return selfHost(user, password);
168 private boolean selfHost(final String user, final String password) {
169 // TODO: m_network_shared = network_shared;
171 if (getSelfHostingActive()) {
172 // TODO: std::cerr << G_STRFUNC << ": Already started." << std::endl;
173 return false; // STARTUPERROR_NONE; //Just do it once.
176 final String dbDirData = getSelfHostingDataPath(false);
177 if (StringUtils.isEmpty(dbDirData) || !fileExists(dbDirData)) {
179 * final String dbDirBackup = dbDir + File.separator + FILENAME_BACKUP;
181 * if(fileExists(dbDirBackup)) { //TODO: std::cerr << G_STRFUNC <<
182 * ": There is no data, but there is backup data." << std::endl; //Let the caller convert the backup to real
183 * data and then try again: return false; // STARTUPERROR_FAILED_NO_DATA_HAS_BACKUP_DATA; } else {
185 // TODO: std::cerr << "ConnectionPool::create_self_hosting(): The data sub-directory could not be found." <<
186 // dbdir_data_uri << std::endl;
187 return false; // STARTUPERROR_FAILED_NO_DATA;
191 final int availablePort = discoverFirstFreePort(PORT_POSTGRESQL_SELF_HOSTED_START,
192 PORT_POSTGRESQL_SELF_HOSTED_END);
193 // std::cout << "debug: " << G_STRFUNC << ":() : debug: Available port for self-hosting: " << available_port <<
195 if (availablePort == 0) {
196 // TODO: Use a return enum or exception so we can tell the user about this:
197 // TODO: std::cerr << G_STRFUNC << ": No port was available between " << PORT_POSTGRESQL_SELF_HOSTED_START
198 // << " and " << PORT_POSTGRESQL_SELF_HOSTED_END << std::endl;
199 return false; // STARTUPERROR_FAILED_UNKNOWN_REASON;
202 final NumberFormat format = NumberFormat.getInstance(Locale.US);
203 format.setGroupingUsed(false); // TODO: Does this change it system-wide?
204 final String portAsText = format.format(availablePort);
206 // -D specifies the data directory.
207 // -c config_file= specifies the configuration file
208 // -k specifies a directory to use for the socket. This must be writable by us.
209 // Make sure to use double quotes for the executable path, because the
210 // CreateProcess() API used on Windows does not support single quotes.
211 final String dbDir = getSelfHostingPath("", false);
212 final String dbDirConfig = getSelfHostingPath("config", false);
213 final String dbDirHba = dbDirConfig + File.separator + "pg_hba.conf";
214 final String dbDirIdent = dbDirConfig + File.separator + "pg_ident.conf";
215 final String dbDirPid = getSelfHostingPath("pid", false);
217 // Note that postgres returns this error if we split the arguments more,
218 // for instance splitting -D and dbDirData into separate strings:
219 // too many command-line arguments (first is "(null)")
220 // Note: If we use "-D " instead of "-D" then the initdb seems to make the space part of the filepath,
221 // though that does not happen with the normal command line.
222 // However, we must have a space after -k.
223 // Also, the c hba_file=path argument must be split after -c, or postgres will get a " hba_file" configuration
224 // parameter instead of "hba_file".
225 final String commandPathStart = getPathToPostgresExecutable("postgres");
226 if(StringUtils.isEmpty(commandPathStart)) {
230 final ProcessBuilder commandPostgresStart = new ProcessBuilder(commandPathStart, "-D"
231 + shellQuote(dbDirData), "-p", portAsText, "-i", // Equivalent to -h "*", which in turn is equivalent
233 // listen_addresses in postgresql.conf. Listen to all IP addresses,
234 // so any client can connect (with a username+password)
235 "-c", "hba_file=" + shellQuote(dbDirHba), "-c", "ident_file=" + shellQuote(dbDirIdent), "-k"
236 + shellQuote(dbDir), "--external_pid_file=" + shellQuote(dbDirPid));
237 // std::cout << G_STRFUNC << ": debug: " << command_postgres_start << std::endl;
239 // Make sure to use double quotes for the executable path, because the
240 // CreateProcess() API used on Windows does not support single quotes.
242 // Note that postgres returns this error if we split the arguments more,
243 // for instance splitting -D and dbDirData into separate strings:
244 // too many command-line arguments (first is "(null)")
245 // Note: If we use "-D " instead of "-D" then the initdb seems to make the space part of the filepath,
246 // though that does not happen with the normal command line.
247 final String commandPathCheck = getPathToPostgresExecutable("pg_ctl");
248 if(StringUtils.isEmpty(commandPathCheck)) {
252 final ProcessBuilder commandCheckPostgresHasStarted = new ProcessBuilder(commandPathCheck,
253 "status", "-D" + shellQuote(dbDirData));
255 // For postgres 8.1, this is "postmaster is running".
256 // For postgres 8.2, this is "server is running".
257 // This is a big hack that we should avoid. murrayc.
259 // pg_ctl actually seems to return a 0 result code for "is running" and a 1 for not running, at least with
261 // so maybe we can avoid this in future.
262 // Please do test it with your postgres version, using "echo $?" to see the result code of the last command.
263 final String secondCommandSuccessText = "is running"; // TODO: This is not a stable API. Also, watch out for
266 // The first command does not return, but the second command can check whether it succeeded:
268 final boolean result = executeCommandLineAndWaitUntilSecondCommandReturnsSuccess(commandPostgresStart,
269 commandCheckPostgresHasStarted, secondCommandSuccessText);
271 // TODO: std::cerr << "Error while attempting to self-host a database." << std::endl;
272 return false; // STARTUPERROR_FAILED_UNKNOWN_REASON;
275 // Remember the port for later:
276 document.setConnectionPort(availablePort);
278 return true; // STARTUPERROR_NONE;
285 private String shellQuote(final String str) {
286 // TODO: If we add the quotes then they seem to be used as part of the path, though that is not a problem with
287 // the normal command line.
291 // return "'" + str + "'";
294 private String getSelfHostingPath(final String subpath, final boolean create) {
295 final String dbDir = document.getSelfHostedDirectoryPath();
296 if (StringUtils.isEmpty(subpath)) {
300 final String dbDirData = dbDir + File.separator + subpath;
301 final File file = new File(dbDirData);
303 // Return the path regardless of whether it exists:
308 if (!file.exists()) {
310 Files.createParentDirs(file);
311 } catch (final IOException e) {
312 // TODO Auto-generated catch block
325 private String getSelfHostingDataPath(final boolean create) {
326 return getSelfHostingPath(FILENAME_DATA, create);
329 private boolean executeCommandLineAndWait(final ProcessBuilder command) {
331 command.redirectErrorStream(true);
333 // Run the first command, and wait for it to return:
334 Process process = null;
336 process = command.start();
337 } catch (final IOException e) {
338 // TODO Auto-generated catch block
343 final InputStream stderr = process.getInputStream();
344 final InputStreamReader isr = new InputStreamReader(stderr);
345 final BufferedReader br = new BufferedReader(isr);
349 while ((line = br.readLine()) != null) {
350 output += line + "\n";
352 } catch (final IOException e1) {
353 e1.printStackTrace();
359 result = process.waitFor();
360 } catch (final InterruptedException e) {
361 // TODO Auto-generated catch block
367 System.out.println("Command failed: " + command.toString());
368 System.out.print("Output: " + output);
375 private boolean executeCommandLineAndWaitUntilSecondCommandReturnsSuccess(final ProcessBuilder command,
376 final ProcessBuilder commandSecond, final String secondCommandSuccessText) {
377 command.redirectErrorStream(true);
379 // Run the first command, and do not wait for it to return:
380 Process process = null;
382 process = command.start();
383 } catch (final IOException e) {
384 // TODO Auto-generated catch block
389 final InputStream stderr = process.getInputStream();
390 final InputStreamReader isr = new InputStreamReader(stderr);
391 final BufferedReader br = new BufferedReader(isr);
394 * We do not wait, because this (postgres, for instance), does not return: final int result = process.waitFor();
395 * if (result != 0) { // TODO: Warn. return false; }
398 // Now run the second command, usually to verify that the first command has really done its work:
399 final boolean result = executeCommandLineAndWait(commandSecond);
401 // Try to get the output:
406 while ((line = br.readLine()) != null) {
407 output += line + "\n";
408 System.out.println(line);
410 } catch (final IOException e1) {
411 // TODO Auto-generated catch block
412 e1.printStackTrace();
416 System.out.print("Output of first command: " + output);
426 private static String getPathToPostgresExecutable(final String string) {
427 final List<String> dirPaths = new ArrayList<String>();
428 dirPaths.add("/usr/bin");
429 dirPaths.add("/usr/lib/postgresql/9.1/bin");
430 dirPaths.add("/usr/lib/postgresql/9.0/bin");
431 dirPaths.add("/usr/lib/postgresql/8.4/bin");
433 for(String dir : dirPaths) {
434 final String path = dir + File.separator + string;
435 if(fileExistsAndIsExecutable(path)) {
447 private static boolean fileExistsAndIsExecutable(String path) {
448 final File file = new File(path);
453 if(!file.canExecute()) {
465 private static int discoverFirstFreePort(final int start, final int end) {
466 for (int port = start; port <= end; ++port) {
468 final ServerSocket socket = new ServerSocket(port);
470 // If the instantiation succeeded then the port was free:
471 return socket.getLocalPort(); // This must equal port.
472 } catch (final IOException ex) {
473 continue; // try next port
484 private static boolean fileExists(final String filePath) {
485 final File file = new File(filePath);
486 return file.exists();
492 private boolean getSelfHostingActive() {
493 return selfHostingActive;
500 private boolean initialize(final String initialUsername, final String initialPassword) {
501 if (!initializeConfFiles()) {
506 // initdb creates a new postgres database cluster:
508 // Get file:// URI for the tmp/ directory:
509 File filePwFile = null;
511 filePwFile = File.createTempFile("glom_initdb_pwfile", "");
512 } catch (final IOException e) {
513 // TODO Auto-generated catch block
516 final String tempPwFile = filePwFile.getPath();
518 final boolean pwfileCreationSucceeded = createTextFile(tempPwFile, initialPassword);
519 if (!pwfileCreationSucceeded) {
524 // Make sure to use double quotes for the executable path, because the
525 // CreateProcess() API used on Windows does not support single quotes.
526 final String dbDirData = getSelfHostingDataPath(false /* create */);
528 // Note that initdb returns this error if we split the arguments more,
529 // for instance splitting -D and dbDirData into separate strings:
530 // too many command-line arguments (first is "(null)")
531 // TODO: If we quote tempPwFile then initdb says that it cannot find it.
532 // Note: If we use "-D " instead of "-D" then the initdb seems to make the space part of the filepath,
533 // though that does not happen with the normal command line.
534 boolean result = false;
535 final String commandPath = getPathToPostgresExecutable("initdb");
536 if(StringUtils.isEmpty(commandPath)) {
539 final ProcessBuilder commandInitdb = new ProcessBuilder(commandPath, "-D"
540 + shellQuote(dbDirData), "-U", initialUsername, "--pwfile=" + tempPwFile);
542 // Note that --pwfile takes the password from the first line of a file. It's an alternative to supplying it when
543 // prompted on stdin.
544 result = executeCommandLineAndWait(commandInitdb);
547 // Of course, we don't want this to stay around. It would be a security risk.
548 final File fileTempPwFile = new File(tempPwFile);
549 if (!fileTempPwFile.delete()) {
554 // TODO: std::cerr << "Error while attempting to create self-hosting database." << std::endl;
558 // Save the username and password for later;
559 this.username = initialUsername;
560 this.password = initialPassword;
562 return result; // ? INITERROR_NONE : INITERROR_COULD_NOT_START_SERVER;
566 private boolean initializeConfFiles() {
567 final String dataDirPath = document.getSelfHostedDirectoryPath();
569 final String dbDirConfig = dataDirPath + File.separator + "config";
570 // String defaultConfContents = "";
572 // Choose the configuration contents based on the postgresql version
573 // and whether we want to be network-shared:
574 // final float postgresqlVersion = 9.0f; //TODO: get_postgresql_utils_version_as_number(slot_progress);
575 // final boolean networkShared = true;
576 // std::cout << "DEBUG: postgresql_version=" << postgresql_version << std::endl;
578 // TODO: Support the other configurations, as in libglom.
579 final String defaultConfContents = DEFAULT_CONFIG_PG_HBA_LOCAL_8p4;
581 // std::cout << "DEBUG: default_conf_contents=" << default_conf_contents << std::endl;
583 final boolean hbaConfCreationSucceeded = createTextFile(dbDirConfig + File.separator + "pg_hba.conf",
584 defaultConfContents);
585 if (!hbaConfCreationSucceeded) {
590 final boolean identConfCreationSucceeded = createTextFile(dbDirConfig + File.separator + "pg_ident.conf",
591 DEFAULT_CONFIG_PG_IDENT);
592 if (!identConfCreationSucceeded) {
605 private static boolean createTextFile(final String path, final String contents) {
606 final File file = new File(path);
607 final File parent = file.getParentFile();
608 if (parent == null) {
615 file.createNewFile();
616 } catch (final IOException e) {
617 // TODO Auto-generated catch block
622 FileOutputStream output = null;
624 output = new FileOutputStream(file);
625 } catch (final FileNotFoundException e) {
626 // TODO Auto-generated catch block
632 output.write(contents.getBytes());
633 } catch (final IOException e) {
634 // TODO Auto-generated catch block
646 private boolean recreateDatabaseFromDocument() {
647 // Check whether the database exists already.
648 final String dbName = document.getConnectionDatabase();
649 if (StringUtils.isEmpty(dbName)) {
653 document.setConnectionDatabase(dbName);
654 Connection connection = createConnection();
655 if (connection != null) {
656 // Connection to the database succeeded, so the database
660 } catch (final SQLException e) {
661 // TODO Auto-generated catch block
667 // Create the database:
669 document.setConnectionDatabase("");
671 connection = createConnection();
672 if (connection == null) {
677 final boolean dbCreated = createDatabase(connection, dbName);
685 // Check that we can connect:
688 } catch (final SQLException e) {
689 // TODO Auto-generated catch block
694 document.setConnectionDatabase(dbName);
695 connection = createConnection();
696 if (connection == null) {
703 // Create each table:
704 final List<String> tables = document.getTableNames();
705 for (final String tableName : tables) {
707 // Create SQL to describe all fields in this table:
708 final List<Field> fields = document.getTableFields(tableName);
711 final boolean tableCreationSucceeded = createTable(connection, document, tableName, fields);
713 if (!tableCreationSucceeded) {
714 // TODO: std::cerr << G_STRFUNC << ": CREATE TABLE failed with the newly-created database." <<
720 // Note that create_database() has already called add_standard_tables() and add_standard_groups(document).
722 // Add groups from the document:
724 if (!addGroupsFromDocument(document)) {
725 // TODO: std::cerr << G_STRFUNC << ": add_groups_from_document() failed." << std::endl;
729 // Set table privileges, using the groups we just added:
731 if (!setTablePrivilegesGroupsFromDocument(document)) {
732 // TODO: std::cerr << G_STRFUNC << ": set_table_privileges_groups_from_document() failed." << std::endl;
736 for (final String tableName : tables) {
737 // Add any example data to the table:
743 final boolean tableInsertSucceeded = insertExampleData(connection, document, tableName);
745 if (!tableInsertSucceeded) {
746 // TODO: std::cerr << G_STRFUNC << ": INSERT of example data failed with the newly-created database." <<
751 // catch(final std::exception& ex)
753 // std::cerr << G_STRFUNC << ": exception: " << ex.what() << std::endl;
759 return true; // All tables created successfully.
764 * @throws SQLException
766 private Connection createConnection() {
767 final Properties connectionProps = new Properties();
768 connectionProps.put("user", this.username);
769 connectionProps.put("password", this.password);
771 String jdbcURL = "jdbc:postgresql://" + document.getConnectionServer() + ":" + document.getConnectionPort();
772 String db = document.getConnectionDatabase();
773 if (StringUtils.isEmpty(db)) {
774 // Use the default PostgreSQL database, because ComboPooledDataSource.connect() fails otherwise.
777 jdbcURL += "/" + db; // TODO: Quote the database name?
779 Connection conn = null;
781 conn = DriverManager.getConnection(jdbcURL + "/", connectionProps);
782 } catch (final SQLException e) {
783 // TODO Auto-generated catch block
784 // e.printStackTrace();
794 private void progress() {
795 // TODO Auto-generated method stub
804 private boolean insertExampleData(final Connection connection, final Document document, final String tableName) {
806 final Factory factory = new Factory(connection, SQLDialect.POSTGRES);
807 final Table<Record> table = Factory.tableByName(tableName);
809 final List<Map<String, DataItem>> exampleRows = document.getExampleRows(tableName);
810 for (final Map<String, DataItem> row : exampleRows) {
811 InsertSetStep<Record> insertStep = factory.insertInto(table);
813 for (final Entry<String, DataItem> entry : row.entrySet()) {
814 final String fieldName = entry.getKey();
815 final DataItem value = entry.getValue();
820 final Field field = document.getField(tableName, fieldName);
825 final org.jooq.Field<Object> jooqField = Factory.fieldByName(field.getName());
826 if (jooqField == null) {
830 final Object fieldValue = value.getValue(field.getGlomType());
831 insertStep = insertStep.set(jooqField, fieldValue);
834 if (!(insertStep instanceof InsertResultStep<?>)) {
838 // We suppress the warning because we _do_ check the cast above.
839 @SuppressWarnings("unchecked")
840 final InsertResultStep<Record> insertResultStep = (InsertResultStep<Record>) insertStep;
843 insertResultStep.fetchOne();
844 } catch (final DataAccessException e) {
845 // e.printStackTrace();
848 // TODO: Check that it worked.
858 private boolean setTablePrivilegesGroupsFromDocument(final Document document2) {
859 // TODO Auto-generated method stub
867 private boolean addGroupsFromDocument(final Document document2) {
868 // TODO Auto-generated method stub
878 private boolean createTable(final Connection connection, final Document document, final String tableName,
879 final List<Field> fields) {
880 boolean tableCreationSucceeded = false;
883 * TODO: //Create the standard field too: //(We don't actually use this yet) if(std::find_if(fields.begin(),
884 * fields.end(), predicate_FieldHasName<Field>(GLOM_STANDARD_FIELD_LOCK)) == fields.end()) { sharedptr<Field>
885 * field = sharedptr<Field>::create(); field->set_name(GLOM_STANDARD_FIELD_LOCK);
886 * field->set_glom_type(Field::TYPE_TEXT); fields.push_back(field); }
889 // Create SQL to describe all fields in this table:
890 String sqlFields = "";
891 for (final Field field : fields) {
892 // Create SQL to describe this field:
893 String sqlFieldDescription = escapeSqlId(field.getName()) + " " + field.getSqlType();
895 if (field.getPrimaryKey()) {
896 sqlFieldDescription += " NOT NULL PRIMARY KEY";
900 if (!StringUtils.isEmpty(sqlFields)) {
904 sqlFields += sqlFieldDescription;
907 if (StringUtils.isEmpty(sqlFields)) {
908 // TODO: std::cerr << G_STRFUNC << ": sql_fields is empty." << std::endl;
911 // Actually create the table
912 final String query = "CREATE TABLE " + escapeSqlId(tableName) + " (" + sqlFields + ");";
913 final Factory factory = new Factory(connection, SQLDialect.POSTGRES);
914 factory.execute(query);
915 tableCreationSucceeded = true;
916 if (!tableCreationSucceeded) {
917 // TODO: Warn: std::cerr << G_STRFUNC << ": CREATE TABLE failed." << std::endl;
920 return tableCreationSucceeded;
927 private String escapeSqlId(final String name) {
929 return "\"" + name + "\"";
935 private static boolean createDatabase(final Connection connection, final String databaseName) {
937 final String query = "CREATE DATABASE \"" + databaseName + "\""; // TODO: Escaping.
938 final Factory factory = new Factory(connection, SQLDialect.POSTGRES);
940 factory.execute(query);
948 public void cleanup() {
951 if ((document != null) && (document.getConnectionPort() != 0)) {
952 final String dbDirData = getSelfHostingDataPath(false);
954 // -D specifies the data directory.
955 // -c config_file= specifies the configuration file
956 // -k specifies a directory to use for the socket. This must be writable by us.
957 // We use "-m fast" instead of the default "-m smart" because that waits for clients to disconnect (and
958 // sometimes never succeeds).
959 // TODO: Warn about connected clients on other computers? Warn those other users?
960 // Make sure to use double quotes for the executable path, because the
961 // CreateProcess() API used on Windows does not support single quotes.
962 final String commandPath = getPathToPostgresExecutable("pg_ctl");
963 if(StringUtils.isEmpty(commandPath)) {
966 final ProcessBuilder commandPostgresStop = new ProcessBuilder(commandPath, "-D"
967 + shellQuote(dbDirData), "stop", "-m", "fast");
968 final boolean result = executeCommandLineAndWait(commandPostgresStop);
975 document.setConnectionPort(0);
979 final String selfhostingPath = getSelfHostingPath("", false);
980 final File fileSelfHosting = new File(selfhostingPath);
981 fileSelfHosting.delete();
983 final String docPath = document.getFileURI();
984 final File fileDoc = new File(docPath);