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;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.net.ServerSocket;
27 import java.sql.Connection;
28 import java.sql.DriverManager;
29 import java.sql.SQLException;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Locale;
34 import java.util.Map.Entry;
35 import java.util.Properties;
37 import org.apache.commons.lang3.StringUtils;
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;
50 import com.ibm.icu.text.NumberFormat;
53 * @author Murray Cumming <murrayc@murrayc.com>
56 public class SelfHoster {
57 // private String tempFilepathDir = "";
58 private boolean selfHostingActive = false;
59 private Document document = null;
60 private String username = "";
61 private String password = "";
63 SelfHoster(final Document document) {
64 this.document = document;
67 private static final int PORT_POSTGRESQL_SELF_HOSTED_START = 5433;
68 private static final int PORT_POSTGRESQL_SELF_HOSTED_END = 5500;
70 private static final String DEFAULT_CONFIG_PG_HBA_LOCAL_8p4 = "# TYPE DATABASE USER CIDR-ADDRESS METHOD\n"
72 + "# local is for Unix domain socket connections only\n"
73 + "# trust allows connection from the current PC without a password:\n"
74 + "local all all trust\n"
75 + "local all all ident\n"
76 + "local all all md5\n"
78 + "# TCP connections from the same computer, with a password:\n"
79 + "host all all 127.0.0.1 255.255.255.255 md5\n"
80 + "# IPv6 local connections:\n"
81 + "host all all ::1/128 md5\n";
83 private static final String DEFAULT_CONFIG_PG_IDENT = "";
84 private static final String FILENAME_DATA = "data";
86 public boolean createAndSelfHostFromExample(final Document.HostingMode hostingMode) {
88 if (!createAndSelfHostNewEmpty(hostingMode)) {
89 // std::cerr << G_STRFUNC << ": test_create_and_selfhost_new_empty() failed." << std::endl;
93 final boolean recreated = recreateDatabaseFromDocument(); /* TODO: Progress callback */
106 * @param subDirectoryPath
109 private boolean createAndSelfHostNewEmpty(final Document.HostingMode hostingMode) {
110 if (hostingMode != Document.HostingMode.HOSTING_MODE_POSTGRES_SELF) {
111 // TODO: std::cerr << G_STRFUNC << ": This test function does not support the specified hosting_mode: " <<
112 // hosting_mode << std::endl;
116 // Save a copy, specifying the path to file in a directory:
117 // For instance, /tmp/testglom/testglom.glom");
118 final String tempFilename = "testglom";
119 final File tempFolder = Files.createTempDir();
120 final File tempDir = new File(tempFolder, tempFilename);
122 final String tempDirPath = tempDir.getPath();
123 final String tempFilePath = tempDirPath + File.separator + tempFilename;
124 final File file = new File(tempFilePath);
126 // Make sure that the file does not exist yet:
131 // Save the example as a real file:
132 document.setFileURI(file.getPath());
134 document.setHostingMode(hostingMode);
135 document.setIsExampleFile(false);
136 final boolean saved = document.save();
138 System.out.println("createAndSelfHostNewEmpty(): Document.save() failed.");
139 return false; // TODO: Delete the directory.
142 // We must specify a default username and password:
143 final String user = "glom_default_developer_user";
144 final String password = "glom_default_developer_password";
146 // Create the self-hosting files:
147 if (!initialize(user, password)) {
148 System.out.println("createAndSelfHostNewEmpty(): initialize failed.");
149 // TODO: Delete directory.
152 // Check that it really created some files:
153 if (!tempDir.exists()) {
154 System.out.println("createAndSelfHostNewEmpty(): tempDir does not exist.");
155 // TODO: Delete directory.
158 return selfHost(user, password);
167 private boolean selfHost(final String user, final String password) {
168 // TODO: m_network_shared = network_shared;
170 if (getSelfHostingActive()) {
171 // TODO: std::cerr << G_STRFUNC << ": Already started." << std::endl;
172 return false; // STARTUPERROR_NONE; //Just do it once.
175 final String dbDirData = getSelfHostingDataPath(false);
176 if (StringUtils.isEmpty(dbDirData) || !fileExists(dbDirData)) {
178 * final String dbDirBackup = dbDir + File.separator + FILENAME_BACKUP;
180 * if(fileExists(dbDirBackup)) { //TODO: std::cerr << G_STRFUNC <<
181 * ": There is no data, but there is backup data." << std::endl; //Let the caller convert the backup to real
182 * data and then try again: return false; // STARTUPERROR_FAILED_NO_DATA_HAS_BACKUP_DATA; } else {
184 // TODO: std::cerr << "ConnectionPool::create_self_hosting(): The data sub-directory could not be found." <<
185 // dbdir_data_uri << std::endl;
186 return false; // STARTUPERROR_FAILED_NO_DATA;
190 final int availablePort = discoverFirstFreePort(PORT_POSTGRESQL_SELF_HOSTED_START,
191 PORT_POSTGRESQL_SELF_HOSTED_END);
192 // std::cout << "debug: " << G_STRFUNC << ":() : debug: Available port for self-hosting: " << available_port <<
194 if (availablePort == 0) {
195 // TODO: Use a return enum or exception so we can tell the user about this:
196 // TODO: std::cerr << G_STRFUNC << ": No port was available between " << PORT_POSTGRESQL_SELF_HOSTED_START
197 // << " and " << PORT_POSTGRESQL_SELF_HOSTED_END << std::endl;
198 return false; // STARTUPERROR_FAILED_UNKNOWN_REASON;
201 final NumberFormat format = NumberFormat.getInstance(Locale.US);
202 format.setGroupingUsed(false); // TODO: Does this change it system-wide?
203 final String portAsText = format.format(availablePort);
205 // -D specifies the data directory.
206 // -c config_file= specifies the configuration file
207 // -k specifies a directory to use for the socket. This must be writable by us.
208 // Make sure to use double quotes for the executable path, because the
209 // CreateProcess() API used on Windows does not support single quotes.
210 final String dbDir = getSelfHostingPath("", false);
211 final String dbDirConfig = getSelfHostingPath("config", false);
212 final String dbDirHba = dbDirConfig + File.separator + "pg_hba.conf";
213 final String dbDirIdent = dbDirConfig + File.separator + "pg_ident.conf";
214 final String dbDirPid = getSelfHostingPath("pid", false);
216 // Note that postgres returns this error if we split the arguments more,
217 // for instance splitting -D and dbDirData into separate strings:
218 // too many command-line arguments (first is "(null)")
219 // Note: If we use "-D " instead of "-D" then the initdb seems to make the space part of the filepath,
220 // though that does not happen with the normal command line.
221 // However, we must have a space after -k.
222 // Also, the c hba_file=path argument must be split after -c, or postgres will get a " hba_file" configuration
223 // parameter instead of "hba_file".
224 final String commandPathStart = getPathToPostgresExecutable("postgres");
225 if (StringUtils.isEmpty(commandPathStart)) {
226 System.out.println("selfHost(): getPathToPostgresExecutable(postgres) failed.");
229 final ProcessBuilder commandPostgresStart = new ProcessBuilder(commandPathStart, "-D" + shellQuote(dbDirData),
230 "-p", portAsText, "-i", // Equivalent to -h "*", which in turn is equivalent
232 // listen_addresses in postgresql.conf. Listen to all IP addresses,
233 // so any client can connect (with a username+password)
234 "-c", "hba_file=" + shellQuote(dbDirHba), "-c", "ident_file=" + shellQuote(dbDirIdent), "-k"
235 + shellQuote(dbDir), "--external_pid_file=" + shellQuote(dbDirPid));
236 // std::cout << G_STRFUNC << ": debug: " << command_postgres_start << std::endl;
238 // Make sure to use double quotes for the executable path, because the
239 // CreateProcess() API used on Windows does not support single quotes.
241 // Note that postgres returns this error if we split the arguments more,
242 // for instance splitting -D and dbDirData into separate strings:
243 // too many command-line arguments (first is "(null)")
244 // Note: If we use "-D " instead of "-D" then the initdb seems to make the space part of the filepath,
245 // though that does not happen with the normal command line.
246 final String commandPathCheck = getPathToPostgresExecutable("pg_ctl");
247 if (StringUtils.isEmpty(commandPathCheck)) {
248 System.out.println("selfHost(): getPathToPostgresExecutable(pg_ctl) failed.");
251 final ProcessBuilder commandCheckPostgresHasStarted = new ProcessBuilder(commandPathCheck, "status", "-D"
252 + shellQuote(dbDirData));
254 // For postgres 8.1, this is "postmaster is running".
255 // For postgres 8.2, this is "server is running".
256 // This is a big hack that we should avoid. murrayc.
258 // pg_ctl actually seems to return a 0 result code for "is running" and a 1 for not running, at least with
260 // so maybe we can avoid this in future.
261 // Please do test it with your postgres version, using "echo $?" to see the result code of the last command.
262 final String secondCommandSuccessText = "is running"; // TODO: This is not a stable API. Also, watch out for
265 // The first command does not return, but the second command can check whether it succeeded:
267 final boolean result = executeCommandLineAndWaitUntilSecondCommandReturnsSuccess(commandPostgresStart,
268 commandCheckPostgresHasStarted, secondCommandSuccessText);
270 System.out.println("selfHost(): Error while attempting to self-host a database.");
271 return false; // STARTUPERROR_FAILED_UNKNOWN_REASON;
274 // Remember the port for later:
275 document.setConnectionPort(availablePort);
277 // Check that we can really connect:
279 //Sleep for a fairly long time initially to avoid distracting error messages when trying to connect,
280 //while the database server is still starting up.
283 } catch (InterruptedException e) {
284 // TODO Auto-generated catch block
288 // pg_ctl sometimes reports success before it is really ready to let us connect,
289 // so in this case we can just keep trying until it works, for a while:
290 for (int i = 0; i < 10; i++) {
294 } catch (InterruptedException e) {
295 // TODO Auto-generated catch block
299 final String dbName = document.getConnectionDatabase();
300 document.setConnectionDatabase(""); // We have not created the database yet.
302 //Check that we can connect:
303 final Connection connection = createConnection(false);
304 document.setConnectionDatabase(dbName);
305 if (connection != null) {
306 //Close the connection:
309 } catch (SQLException e) {
310 // TODO Auto-generated catch block
314 System.out.println("selfHost(): Connection succeeded after retries=" + i);
315 return true; // STARTUPERROR_NONE;
319 .println("selfHost(): Waiting and retrying the connection due to suspected too-early success of pg_ctl. retries="
323 System.out.println("selfHost(): Test connection failed after multiple retries.");
331 private String shellQuote(final String str) {
332 // TODO: If we add the quotes then they seem to be used as part of the path, though that is not a problem with
333 // the normal command line.
337 // return "'" + str + "'";
340 private String getSelfHostingPath(final String subpath, final boolean create) {
341 final String dbDir = document.getSelfHostedDirectoryPath();
342 if (StringUtils.isEmpty(subpath)) {
346 final String dbDirData = dbDir + File.separator + subpath;
347 final File file = new File(dbDirData);
349 // Return the path regardless of whether it exists:
354 if (!file.exists()) {
356 Files.createParentDirs(file);
357 } catch (final IOException e) {
358 // TODO Auto-generated catch block
371 private String getSelfHostingDataPath(final boolean create) {
372 return getSelfHostingPath(FILENAME_DATA, create);
375 private boolean executeCommandLineAndWait(final ProcessBuilder command) {
377 command.redirectErrorStream(true);
379 // Run the first command, and 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);
392 // String output = "";
395 * try { //TODO: readLine() can hang, waiting for an end of line that never comes. while ((line = br.readLine())
396 * != null) { output += line + "\n"; } } catch (final IOException e1) { e1.printStackTrace(); return false; }
401 result = process.waitFor();
402 } catch (final InterruptedException e) {
403 // TODO Auto-generated catch block
409 System.out.println("executeCommandLineAndWait(): Command failed: " + command.command().toString());
410 // System.out.print(" Output: " + output);
417 private boolean executeCommandLineAndWaitUntilSecondCommandReturnsSuccess(final ProcessBuilder command,
418 final ProcessBuilder commandSecond, final String secondCommandSuccessText) {
419 command.redirectErrorStream(true);
421 // Run the first command, and do not wait for it to return:
422 // Process process = null;
426 } catch (final IOException e) {
427 // TODO Auto-generated catch block
432 // final InputStream stderr = process.getInputStream();
433 // final InputStreamReader isr = new InputStreamReader(stderr);
434 // final BufferedReader br = new BufferedReader(isr);
437 * We do not wait, because this (postgres, for instance), does not return: final int result = process.waitFor();
438 * if (result != 0) { // TODO: Warn. return false; }
441 // Now run the second command, usually to verify that the first command has really done its work:
442 // We run this repeatedly until it succeeds, to show that the first command has finished.
443 boolean result = false;
445 result = executeCommandLineAndWait(commandSecond);
447 System.out.println("executeCommandLineAndWait(): second command succeeded.");
452 } catch (InterruptedException e) {
453 // TODO Auto-generated catch block
458 System.out.println("executeCommandLineAndWait(): Trying the second command again.");
462 // Try to get the output:
464 * if (!result) { String output = ""; /* String line; try { // TODO: readLine() can hang, waiting for an end of
465 * line that never comes. while ((line = br.readLine()) != null) { output += line + "\n";
466 * System.out.println(line); } } catch (final IOException e1) { // TODO Auto-generated catch block
467 * e1.printStackTrace(); return false; }
470 // System.out.println(" Output of first command: " + output);
471 // System.out.println(" first command: " + command.command().toString());
472 // System.out.println(" second command: " + commandSecond.command().toString());
480 private static String getPathToPostgresExecutable(final String string) {
481 final List<String> dirPaths = new ArrayList<String>();
482 dirPaths.add("/usr/bin");
483 dirPaths.add("/usr/lib/postgresql/9.1/bin");
484 dirPaths.add("/usr/lib/postgresql/9.0/bin");
485 dirPaths.add("/usr/lib/postgresql/8.4/bin");
487 for (String dir : dirPaths) {
488 final String path = dir + File.separator + string;
489 if (fileExistsAndIsExecutable(path)) {
501 private static boolean fileExistsAndIsExecutable(String path) {
502 final File file = new File(path);
503 if (!file.exists()) {
507 if (!file.canExecute()) {
519 private static int discoverFirstFreePort(final int start, final int end) {
520 for (int port = start; port <= end; ++port) {
522 final ServerSocket socket = new ServerSocket(port);
524 // If the instantiation succeeded then the port was free:
525 final int result = socket.getLocalPort(); // This must equal port.
528 } catch (final IOException ex) {
529 continue; // try next port
540 private static boolean fileExists(final String filePath) {
541 final File file = new File(filePath);
542 return file.exists();
548 private boolean getSelfHostingActive() {
549 return selfHostingActive;
556 private boolean initialize(final String initialUsername, final String initialPassword) {
557 if (!initializeConfFiles()) {
558 System.out.println("initialize(): initializeConfFiles() failed.");
562 // initdb creates a new postgres database cluster:
564 // Get file:// URI for the tmp/ directory:
565 File filePwFile = null;
567 filePwFile = File.createTempFile("glom_initdb_pwfile", "");
568 } catch (final IOException e) {
569 // TODO Auto-generated catch block
572 final String tempPwFile = filePwFile.getPath();
574 final boolean pwfileCreationSucceeded = createTextFile(tempPwFile, initialPassword);
575 if (!pwfileCreationSucceeded) {
576 System.out.println("initialize(): createTextFile() failed.");
580 // Make sure to use double quotes for the executable path, because the
581 // CreateProcess() API used on Windows does not support single quotes.
582 final String dbDirData = getSelfHostingDataPath(false /* create */);
584 // Note that initdb returns this error if we split the arguments more,
585 // for instance splitting -D and dbDirData into separate strings:
586 // too many command-line arguments (first is "(null)")
587 // TODO: If we quote tempPwFile then initdb says that it cannot find it.
588 // Note: If we use "-D " instead of "-D" then the initdb seems to make the space part of the filepath,
589 // though that does not happen with the normal command line.
590 boolean result = false;
591 final String commandPath = getPathToPostgresExecutable("initdb");
592 if (StringUtils.isEmpty(commandPath)) {
593 System.out.println("initialize(): getPathToPostgresExecutable(initdb) failed.");
595 final ProcessBuilder commandInitdb = new ProcessBuilder(commandPath, "-D" + shellQuote(dbDirData), "-U",
596 initialUsername, "--pwfile=" + tempPwFile);
598 // Note that --pwfile takes the password from the first line of a file. It's an alternative to supplying it
600 // prompted on stdin.
601 result = executeCommandLineAndWait(commandInitdb);
604 // Of course, we don't want this to stay around. It would be a security risk.
605 final File fileTempPwFile = new File(tempPwFile);
606 if (!fileTempPwFile.delete()) {
607 System.out.println("initialize(): Failed to delete the password file.");
611 System.out.println("initialize(): Error while attempting to create self-hosting database.");
615 // Save the username and password for later;
616 this.username = initialUsername;
617 this.password = initialPassword;
619 return result; // ? INITERROR_NONE : INITERROR_COULD_NOT_START_SERVER;
623 private boolean initializeConfFiles() {
624 final String dataDirPath = document.getSelfHostedDirectoryPath();
626 final String dbDirConfig = dataDirPath + File.separator + "config";
627 // String defaultConfContents = "";
629 // Choose the configuration contents based on the postgresql version
630 // and whether we want to be network-shared:
631 // final float postgresqlVersion = 9.0f; //TODO: get_postgresql_utils_version_as_number(slot_progress);
632 // final boolean networkShared = true;
633 // std::cout << "DEBUG: postgresql_version=" << postgresql_version << std::endl;
635 // TODO: Support the other configurations, as in libglom.
636 final String defaultConfContents = DEFAULT_CONFIG_PG_HBA_LOCAL_8p4;
638 // std::cout << "DEBUG: default_conf_contents=" << default_conf_contents << std::endl;
640 final boolean hbaConfCreationSucceeded = createTextFile(dbDirConfig + File.separator + "pg_hba.conf",
641 defaultConfContents);
642 if (!hbaConfCreationSucceeded) {
643 System.out.println("initialize(): createTextFile() failed.");
647 final boolean identConfCreationSucceeded = createTextFile(dbDirConfig + File.separator + "pg_ident.conf",
648 DEFAULT_CONFIG_PG_IDENT);
649 if (!identConfCreationSucceeded) {
650 System.out.println("initialize(): createTextFile() failed.");
662 private static boolean createTextFile(final String path, final String contents) {
663 final File file = new File(path);
664 final File parent = file.getParentFile();
665 if (parent == null) {
666 System.out.println("initialize(): getParentFile() failed.");
672 file.createNewFile();
673 } catch (final IOException e) {
674 // TODO Auto-generated catch block
679 FileOutputStream output = null;
681 output = new FileOutputStream(file);
682 } catch (final FileNotFoundException e) {
683 // TODO Auto-generated catch block
689 output.write(contents.getBytes());
690 } catch (final IOException e) {
691 // TODO Auto-generated catch block
694 //TODO: Avoid the duplicate close() here.
697 } catch (IOException e1) {
698 // TODO Auto-generated catch block
699 e1.printStackTrace();
707 } catch (IOException e) {
708 // TODO Auto-generated catch block
719 private boolean recreateDatabaseFromDocument() {
720 // Check whether the database exists already.
721 final String dbName = document.getConnectionDatabase();
722 if (StringUtils.isEmpty(dbName)) {
726 document.setConnectionDatabase(dbName);
727 Connection connection = createConnection(true);
728 if (connection != null) {
729 // Connection to the database succeeded, so the database
733 } catch (final SQLException e) {
734 // TODO Auto-generated catch block
740 // Create the database:
742 document.setConnectionDatabase("");
744 connection = createConnection(false);
745 if (connection == null) {
746 System.out.println("recreatedDatabase(): createConnection() failed, before creating the database.");
750 final boolean dbCreated = createDatabase(connection, dbName);
758 // Check that we can connect:
761 } catch (final SQLException e) {
762 // TODO Auto-generated catch block
767 document.setConnectionDatabase(dbName);
768 connection = createConnection(false);
769 if (connection == null) {
770 System.out.println("recreatedDatabase(): createConnection() failed, after creating the database.");
776 // Create each table:
777 final List<String> tables = document.getTableNames();
778 for (final String tableName : tables) {
780 // Create SQL to describe all fields in this table:
781 final List<Field> fields = document.getTableFields(tableName);
784 final boolean tableCreationSucceeded = createTable(connection, document, tableName, fields);
786 if (!tableCreationSucceeded) {
787 // TODO: std::cerr << G_STRFUNC << ": CREATE TABLE failed with the newly-created database." <<
793 // Note that create_database() has already called add_standard_tables() and add_standard_groups(document).
795 // Add groups from the document:
797 if (!addGroupsFromDocument(document)) {
798 // TODO: std::cerr << G_STRFUNC << ": add_groups_from_document() failed." << std::endl;
802 // Set table privileges, using the groups we just added:
804 if (!setTablePrivilegesGroupsFromDocument(document)) {
805 // TODO: std::cerr << G_STRFUNC << ": set_table_privileges_groups_from_document() failed." << std::endl;
809 for (final String tableName : tables) {
810 // Add any example data to the table:
816 final boolean tableInsertSucceeded = insertExampleData(connection, document, tableName);
818 if (!tableInsertSucceeded) {
819 // TODO: std::cerr << G_STRFUNC << ": INSERT of example data failed with the newly-created database." <<
824 // catch(final std::exception& ex)
826 // std::cerr << G_STRFUNC << ": exception: " << ex.what() << std::endl;
832 return true; // All tables created successfully.
837 public Connection createConnection(boolean failureExpected) {
838 final Properties connectionProps = new Properties();
839 connectionProps.put("user", this.username);
840 connectionProps.put("password", this.password);
842 String jdbcURL = "jdbc:postgresql://" + document.getConnectionServer() + ":" + document.getConnectionPort();
843 String db = document.getConnectionDatabase();
844 if (StringUtils.isEmpty(db)) {
845 // Use the default PostgreSQL database, because ComboPooledDataSource.connect() fails otherwise.
848 jdbcURL += "/" + db; // TODO: Quote the database name?
850 Connection conn = null;
852 //TODO: Remove these debug prints when we figure out why getConnection sometimes hangs.
853 //System.out.println("debug: SelfHoster.createConnection(): before createConnection()");
854 DriverManager.setLoginTimeout(10);
855 conn = DriverManager.getConnection(jdbcURL + "/", connectionProps);
856 //System.out.println("debug: SelfHoster.createConnection(): before createConnection()");
857 } catch (final SQLException e) {
858 if(!failureExpected) {
870 private void progress() {
871 // TODO Auto-generated method stub
880 private boolean insertExampleData(final Connection connection, final Document document, final String tableName) {
882 final Factory factory = new Factory(connection, SQLDialect.POSTGRES);
883 final Table<Record> table = Factory.tableByName(tableName);
885 final List<Map<String, DataItem>> exampleRows = document.getExampleRows(tableName);
886 for (final Map<String, DataItem> row : exampleRows) {
887 InsertSetStep<Record> insertStep = factory.insertInto(table);
889 for (final Entry<String, DataItem> entry : row.entrySet()) {
890 final String fieldName = entry.getKey();
891 final DataItem value = entry.getValue();
896 final Field field = document.getField(tableName, fieldName);
901 final org.jooq.Field<Object> jooqField = Factory.fieldByName(field.getName());
902 if (jooqField == null) {
906 final Object fieldValue = value.getValue(field.getGlomType());
907 insertStep = insertStep.set(jooqField, fieldValue);
910 if (!(insertStep instanceof InsertResultStep<?>)) {
914 // We suppress the warning because we _do_ check the cast above.
915 @SuppressWarnings("unchecked")
916 final InsertResultStep<Record> insertResultStep = (InsertResultStep<Record>) insertStep;
919 insertResultStep.fetchOne();
920 } catch (final DataAccessException e) {
921 // e.printStackTrace();
924 // TODO: Check that it worked.
934 private boolean setTablePrivilegesGroupsFromDocument(final Document document2) {
935 // TODO Auto-generated method stub
943 private boolean addGroupsFromDocument(final Document document2) {
944 // TODO Auto-generated method stub
954 private boolean createTable(final Connection connection, final Document document, final String tableName,
955 final List<Field> fields) {
956 boolean tableCreationSucceeded = false;
959 * TODO: //Create the standard field too: //(We don't actually use this yet) if(std::find_if(fields.begin(),
960 * fields.end(), predicate_FieldHasName<Field>(GLOM_STANDARD_FIELD_LOCK)) == fields.end()) { sharedptr<Field>
961 * field = sharedptr<Field>::create(); field->set_name(GLOM_STANDARD_FIELD_LOCK);
962 * field->set_glom_type(Field::TYPE_TEXT); fields.push_back(field); }
965 // Create SQL to describe all fields in this table:
966 String sqlFields = "";
967 for (final Field field : fields) {
968 // Create SQL to describe this field:
969 String sqlFieldDescription = escapeSqlId(field.getName()) + " " + field.getSqlType();
971 if (field.getPrimaryKey()) {
972 sqlFieldDescription += " NOT NULL PRIMARY KEY";
976 if (!StringUtils.isEmpty(sqlFields)) {
980 sqlFields += sqlFieldDescription;
983 if (StringUtils.isEmpty(sqlFields)) {
984 // TODO: std::cerr << G_STRFUNC << ": sql_fields is empty." << std::endl;
987 // Actually create the table
988 final String query = "CREATE TABLE " + escapeSqlId(tableName) + " (" + sqlFields + ");";
989 final Factory factory = new Factory(connection, SQLDialect.POSTGRES);
990 factory.execute(query);
991 tableCreationSucceeded = true;
992 if (!tableCreationSucceeded) {
993 System.out.println("recreatedDatabase(): CREATE TABLE() failed.");
996 return tableCreationSucceeded;
1003 private String escapeSqlId(final String name) {
1005 return "\"" + name + "\"";
1011 private static boolean createDatabase(final Connection connection, final String databaseName) {
1013 final String query = "CREATE DATABASE \"" + databaseName + "\""; // TODO: Escaping.
1014 final Factory factory = new Factory(connection, SQLDialect.POSTGRES);
1016 factory.execute(query);
1024 public boolean cleanup() {
1025 boolean result = true;
1028 if ((document != null) && (document.getConnectionPort() != 0)) {
1029 final String dbDirData = getSelfHostingDataPath(false);
1031 // -D specifies the data directory.
1032 // -c config_file= specifies the configuration file
1033 // -k specifies a directory to use for the socket. This must be writable by us.
1034 // We use "-m fast" instead of the default "-m smart" because that waits for clients to disconnect (and
1035 // sometimes never succeeds).
1036 // TODO: Warn about connected clients on other computers? Warn those other users?
1037 // Make sure to use double quotes for the executable path, because the
1038 // CreateProcess() API used on Windows does not support single quotes.
1039 final String commandPath = getPathToPostgresExecutable("pg_ctl");
1040 if (StringUtils.isEmpty(commandPath)) {
1041 System.out.println("cleanup(): getPathToPostgresExecutable(pg_ctl) failed.");
1043 final ProcessBuilder commandPostgresStop = new ProcessBuilder(commandPath,
1044 "-D" + shellQuote(dbDirData), "stop", "-m", "fast");
1045 result = executeCommandLineAndWait(commandPostgresStop);
1047 System.out.println("cleanup(): Failed to stop the PostgreSQL server.");
1051 document.setConnectionPort(0);
1054 // Delete the files:
1055 final String selfhostingPath = getSelfHostingPath("", false);
1056 final File fileSelfHosting = new File(selfhostingPath);
1057 fileSelfHosting.delete();
1059 final String docPath = document.getFileURI();
1060 final File fileDoc = new File(docPath);
1066 public String getUsername() {
1070 public String getPassword() {