Replace deprecated DOM.setElementAttribute().
[online-glom:gwt-glom.git] / src / test / java / org / glom / web / server / SelfHosterPostgreSQL.java
1 /*
2  * Copyright (C) 2012 Openismus GmbH
3  *
4  * This file is part of GWT-Glom.
5  *
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.
10  *
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
14  * for more details.
15  *
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/>.
18  */
19
20 package org.glom.web.server;
21
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.sql.Connection;
27 import java.sql.DriverManager;
28 import java.sql.SQLException;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Properties;
32
33 import org.apache.commons.lang3.StringUtils;
34 import org.glom.web.server.libglom.Document;
35 import org.glom.web.shared.libglom.Field;
36 import org.jooq.SQLDialect;
37 import org.jooq.impl.Factory;
38
39 import com.google.common.io.Files;
40
41 /**
42  * @author Murray Cumming <murrayc@murrayc.com>
43  * 
44  */
45 public class SelfHosterPostgreSQL extends SelfHoster {
46         SelfHosterPostgreSQL(final Document document) {
47                 super(document);
48         }
49
50         private static final int PORT_POSTGRESQL_SELF_HOSTED_START = 5433;
51         private static final int PORT_POSTGRESQL_SELF_HOSTED_END = 5500;
52
53         private static final String DEFAULT_CONFIG_PG_HBA_LOCAL_8p4 = "# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD\n"
54                         + "\n"
55                         + "# local is for Unix domain socket connections only\n"
56                         + "# trust allows connection from the current PC without a password:\n"
57                         + "local   all         all                               trust\n"
58                         + "local   all         all                               ident\n"
59                         + "local   all         all                               md5\n"
60                         + "\n"
61                         + "# TCP connections from the same computer, with a password:\n"
62                         + "host    all         all         127.0.0.1    255.255.255.255    md5\n"
63                         + "# IPv6 local connections:\n"
64                         + "host    all         all         ::1/128               md5\n";
65
66         private static final String DEFAULT_CONFIG_PG_IDENT = "";
67         private static final String FILENAME_DATA = "data";
68
69         /**
70          * @param document
71          * @param
72          * @param subDirectoryPath
73          * @return
74          * @Override
75          */
76         protected boolean createAndSelfHostNewEmpty() {
77                 final File tempDir = saveDocumentCopy(Document.HostingMode.HOSTING_MODE_POSTGRES_SELF);
78
79                 // We must specify a default username and password:
80                 final String user = "glom_default_developer_user";
81                 final String password = "glom_default_developer_password";
82
83                 // Create the self-hosting files:
84                 if (!initialize(user, password)) {
85                         System.out.println("createAndSelfHostNewEmpty(): initialize failed.");
86                         // TODO: Delete directory.
87                 }
88
89                 // Check that it really created some files:
90                 if (!tempDir.exists()) {
91                         System.out.println("createAndSelfHostNewEmpty(): tempDir does not exist.");
92                         // TODO: Delete directory.
93                 }
94
95                 return selfHost(user, password);
96         }
97
98         /**
99          * @param document
100          * @param user
101          * @param password
102          * @return
103          * @Override
104          */
105         private boolean selfHost(final String user, final String password) {
106                 // TODO: m_network_shared = network_shared;
107
108                 if (getSelfHostingActive()) {
109                         // TODO: std::cerr << G_STRFUNC << ": Already started." << std::endl;
110                         return false; // STARTUPERROR_NONE; //Just do it once.
111                 }
112
113                 final String dbDirData = getSelfHostingDataPath(false);
114                 if (StringUtils.isEmpty(dbDirData) || !SelfHoster.fileExists(dbDirData)) {
115                         /*
116                          * final String dbDirBackup = dbDir + File.separator + FILENAME_BACKUP;
117                          * 
118                          * if(fileExists(dbDirBackup)) { //TODO: std::cerr << G_STRFUNC <<
119                          * ": There is no data, but there is backup data." << std::endl; //Let the caller convert the backup to real
120                          * data and then try again: return false; // STARTUPERROR_FAILED_NO_DATA_HAS_BACKUP_DATA; } else {
121                          */
122                         // TODO: std::cerr << "ConnectionPool::create_self_hosting(): The data sub-directory could not be found." <<
123                         // dbdir_data_uri << std::endl;
124                         return false; // STARTUPERROR_FAILED_NO_DATA;
125                         // }
126                 }
127
128                 final int availablePort = SelfHoster.discoverFirstFreePort(PORT_POSTGRESQL_SELF_HOSTED_START,
129                                 PORT_POSTGRESQL_SELF_HOSTED_END);
130                 // std::cout << "debug: " << G_STRFUNC << ":() : debug: Available port for self-hosting: " << available_port <<
131                 // std::endl;
132                 if (availablePort == 0) {
133                         // TODO: Use a return enum or exception so we can tell the user about this:
134                         // TODO: std::cerr << G_STRFUNC << ": No port was available between " << PORT_POSTGRESQL_SELF_HOSTED_START
135                         // << " and " << PORT_POSTGRESQL_SELF_HOSTED_END << std::endl;
136                         return false; // STARTUPERROR_FAILED_UNKNOWN_REASON;
137                 }
138
139                 final String portAsText = portNumberAsText(availablePort);
140
141                 // -D specifies the data directory.
142                 // -c config_file= specifies the configuration file
143                 // -k specifies a directory to use for the socket. This must be writable by us.
144                 // Make sure to use double quotes for the executable path, because the
145                 // CreateProcess() API used on Windows does not support single quotes.
146                 final String dbDir = getSelfHostingPath("", false);
147                 final String dbDirConfig = getSelfHostingPath("config", false);
148                 final String dbDirHba = dbDirConfig + File.separator + "pg_hba.conf";
149                 final String dbDirIdent = dbDirConfig + File.separator + "pg_ident.conf";
150                 final String dbDirPid = getSelfHostingPath("pid", false);
151
152                 // Note that postgres returns this error if we split the arguments more,
153                 // for instance splitting -D and dbDirData into separate strings:
154                 // too many command-line arguments (first is "(null)")
155                 // Note: If we use "-D " instead of "-D" then the initdb seems to make the space part of the filepath,
156                 // though that does not happen with the normal command line.
157                 // However, we must have a space after -k.
158                 // Also, the c hba_file=path argument must be split after -c, or postgres will get a " hba_file" configuration
159                 // parameter instead of "hba_file".
160                 final String commandPathStart = getPathToPostgresExecutable("postgres");
161                 if (StringUtils.isEmpty(commandPathStart)) {
162                         System.out.println("selfHost(): getPathToPostgresExecutable(postgres) failed.");
163                         return false;
164                 }
165                 final ProcessBuilder commandPostgresStart = new ProcessBuilder(commandPathStart, "-D" + shellQuote(dbDirData),
166                                 "-p", portAsText, "-i", // Equivalent to -h "*", which in turn is equivalent
167                                                                                 // to
168                                 // listen_addresses in postgresql.conf. Listen to all IP addresses,
169                                 // so any client can connect (with a username+password)
170                                 "-c", "hba_file=" + shellQuote(dbDirHba), "-c", "ident_file=" + shellQuote(dbDirIdent), "-k"
171                                                 + shellQuote(dbDir), "--external_pid_file=" + shellQuote(dbDirPid));
172                 // std::cout << G_STRFUNC << ": debug: " << command_postgres_start << std::endl;
173
174                 // Make sure to use double quotes for the executable path, because the
175                 // CreateProcess() API used on Windows does not support single quotes.
176                 //
177                 // Note that postgres returns this error if we split the arguments more,
178                 // for instance splitting -D and dbDirData into separate strings:
179                 // too many command-line arguments (first is "(null)")
180                 // Note: If we use "-D " instead of "-D" then the initdb seems to make the space part of the filepath,
181                 // though that does not happen with the normal command line.
182                 final String commandPathCheck = getPathToPostgresExecutable("pg_ctl");
183                 if (StringUtils.isEmpty(commandPathCheck)) {
184                         System.out.println("selfHost(): getPathToPostgresExecutable(pg_ctl) failed.");
185                         return false;
186                 }
187                 final ProcessBuilder commandCheckPostgresHasStarted = new ProcessBuilder(commandPathCheck, "status", "-D"
188                                 + shellQuote(dbDirData));
189
190                 // For postgres 8.1, this is "postmaster is running".
191                 // For postgres 8.2, this is "server is running".
192                 // This is a big hack that we should avoid. murrayc.
193                 //
194                 // pg_ctl actually seems to return a 0 result code for "is running" and a 1 for not running, at least with
195                 // Postgres 8.2,
196                 // so maybe we can avoid this in future.
197                 // Please do test it with your postgres version, using "echo $?" to see the result code of the last command.
198                 final String secondCommandSuccessText = "is running"; // TODO: This is not a stable API. Also, watch out for
199                                                                                                                                 // localisation.
200
201                 // The first command does not return, but the second command can check whether it succeeded:
202                 // TODO: Progress
203                 final boolean result = executeCommandLineAndWaitUntilSecondCommandReturnsSuccess(commandPostgresStart,
204                                 commandCheckPostgresHasStarted, secondCommandSuccessText);
205                 if (!result) {
206                         System.out.println("selfHost(): Error while attempting to self-host a database.");
207                         return false; // STARTUPERROR_FAILED_UNKNOWN_REASON;
208                 }
209
210                 // Remember the port for later:
211                 document.setConnectionPort(availablePort);
212
213                 // Check that we can really connect:
214                 
215                 //Sleep for a fairly long time initially to avoid distracting error messages when trying to connect,
216                 //while the database server is still starting up.
217                 try {
218                         Thread.sleep(5000);
219                 } catch (InterruptedException e) {
220                         // TODO Auto-generated catch block
221                         e.printStackTrace();
222                 }
223
224                 // pg_ctl sometimes reports success before it is really ready to let us connect,
225                 // so in this case we can just keep trying until it works, for a while:
226                 for (int i = 0; i < 10; i++) {
227
228                         try {
229                                 Thread.sleep(1000);
230                         } catch (InterruptedException e) {
231                                 // TODO Auto-generated catch block
232                                 e.printStackTrace();
233                         }
234
235                         final String dbName = document.getConnectionDatabase();
236                         document.setConnectionDatabase(""); // We have not created the database yet.
237
238                         //Check that we can connect:
239                         final Connection connection = createConnection(false);
240                         document.setConnectionDatabase(dbName);
241                         if (connection != null) {
242                                 //Close the connection:
243                                 try {
244                                         connection.close();
245                                 } catch (SQLException e) {
246                                         // TODO Auto-generated catch block
247                                         e.printStackTrace();
248                                 }
249
250                                 System.out.println("selfHost(): Connection succeeded after retries=" + i);
251                                 return true; // STARTUPERROR_NONE;
252                         }
253
254                         System.out
255                                         .println("selfHost(): Waiting and retrying the connection due to suspected too-early success of pg_ctl. retries="
256                                                         + i);
257                 }
258
259                 System.out.println("selfHost(): Test connection failed after multiple retries.");
260                 return false;
261         }
262
263         private String getSelfHostingPath(final String subpath, final boolean create) {
264                 final String dbDir = document.getSelfHostedDirectoryPath();
265                 if (StringUtils.isEmpty(subpath)) {
266                         return dbDir;
267                 }
268
269                 final String dbDirData = dbDir + File.separator + subpath;
270                 final File file = new File(dbDirData);
271
272                 // Return the path regardless of whether it exists:
273                 if (!create) {
274                         return dbDirData;
275                 }
276
277                 if (!file.exists()) {
278                         try {
279                                 Files.createParentDirs(file);
280                         } catch (final IOException e) {
281                                 // TODO Auto-generated catch block
282                                 e.printStackTrace();
283                                 return "";
284                         }
285
286                         if (!file.mkdir()) {
287                                 return "";
288                         }
289                 }
290
291                 return dbDirData;
292         }
293
294         private String getSelfHostingDataPath(final boolean create) {
295                 return getSelfHostingPath(FILENAME_DATA, create);
296         }
297
298         /**
299          * @param string
300          * @return
301          */
302         private static String getPathToPostgresExecutable(final String string) {
303                 final List<String> dirPaths = new ArrayList<String>();
304                 dirPaths.add("/usr/bin");
305                 dirPaths.add("/usr/lib/postgresql/9.1/bin");
306                 dirPaths.add("/usr/lib/postgresql/9.0/bin");
307                 dirPaths.add("/usr/lib/postgresql/8.4/bin");
308
309                 for (String dir : dirPaths) {
310                         final String path = dir + File.separator + string;
311                         if (fileExistsAndIsExecutable(path)) {
312                                 return path;
313                         }
314                 }
315
316                 return "";
317         }
318
319         /**
320          * @param cpds
321          * @return
322          */
323         private boolean initialize(final String initialUsername, final String initialPassword) {
324                 if (!initializeConfFiles()) {
325                         System.out.println("initialize(): initializeConfFiles() failed.");
326                         return false;
327                 }
328                 
329                 if (StringUtils.isEmpty(initialUsername)) {
330                         System.out.println("initialize(): initialUsername is empty.");
331                         return false;
332                 }
333
334                 if (StringUtils.isEmpty(initialPassword)) {
335                         System.out.println("initialize(): initialPassword is empty.");
336                         return false;
337                 }
338
339                 // initdb creates a new postgres database cluster:
340
341                 // Get file:// URI for the tmp/ directory:
342                 File filePwFile = null;
343                 try {
344                         filePwFile = File.createTempFile("glom_initdb_pwfile", "");
345                 } catch (final IOException e) {
346                         // TODO Auto-generated catch block
347                         e.printStackTrace();
348                 }
349                 final String tempPwFile = filePwFile.getPath();
350
351                 final boolean pwfileCreationSucceeded = createTextFile(tempPwFile, initialPassword);
352                 if (!pwfileCreationSucceeded) {
353                         System.out.println("initialize(): createTextFile() failed.");
354                         return false;
355                 }
356
357                 // Make sure to use double quotes for the executable path, because the
358                 // CreateProcess() API used on Windows does not support single quotes.
359                 final String dbDirData = getSelfHostingDataPath(false /* create */);
360
361                 // Note that initdb returns this error if we split the arguments more,
362                 // for instance splitting -D and dbDirData into separate strings:
363                 // too many command-line arguments (first is "(null)")
364                 // TODO: If we quote tempPwFile then initdb says that it cannot find it.
365                 // Note: If we use "-D " instead of "-D" then the initdb seems to make the space part of the filepath,
366                 // though that does not happen with the normal command line.
367                 boolean result = false;
368                 final String commandPath = getPathToPostgresExecutable("initdb");
369                 if (StringUtils.isEmpty(commandPath)) {
370                         System.out.println("initialize(): getPathToPostgresExecutable(initdb) failed.");
371                 } else {
372                         final ProcessBuilder commandInitdb = new ProcessBuilder(commandPath, "-D" + shellQuote(dbDirData), "-U",
373                                         initialUsername, "--pwfile=" + tempPwFile);
374
375                         // Note that --pwfile takes the password from the first line of a file. It's an alternative to supplying it
376                         // when
377                         // prompted on stdin.
378                         result = executeCommandLineAndWait(commandInitdb);
379                 }
380
381                 // Of course, we don't want this to stay around. It would be a security risk.
382                 final File fileTempPwFile = new File(tempPwFile);
383                 if (!fileTempPwFile.delete()) {
384                         System.out.println("initialize(): Failed to delete the password file.");
385                 }
386
387                 if (!result) {
388                         System.out.println("initialize(): Error while attempting to create self-hosting database.");
389                         return false;
390                 }
391
392                 // Save the username and password for later;
393                 this.username = initialUsername;
394                 this.password = initialPassword;
395
396                 return result; // ? INITERROR_NONE : INITERROR_COULD_NOT_START_SERVER;
397
398         }
399
400         private boolean initializeConfFiles() {
401                 final String dataDirPath = document.getSelfHostedDirectoryPath();
402
403                 final String dbDirConfig = dataDirPath + File.separator + "config";
404                 // String defaultConfContents = "";
405
406                 // Choose the configuration contents based on the postgresql version
407                 // and whether we want to be network-shared:
408                 // final float postgresqlVersion = 9.0f; //TODO: get_postgresql_utils_version_as_number(slot_progress);
409                 // final boolean networkShared = true;
410                 // std::cout << "DEBUG: postgresql_version=" << postgresql_version << std::endl;
411
412                 // TODO: Support the other configurations, as in libglom.
413                 final String defaultConfContents = DEFAULT_CONFIG_PG_HBA_LOCAL_8p4;
414
415                 // std::cout << "DEBUG: default_conf_contents=" << default_conf_contents << std::endl;
416
417                 final boolean hbaConfCreationSucceeded = createTextFile(dbDirConfig + File.separator + "pg_hba.conf",
418                                 defaultConfContents);
419                 if (!hbaConfCreationSucceeded) {
420                         System.out.println("initialize(): createTextFile() failed.");
421                         return false;
422                 }
423
424                 final boolean identConfCreationSucceeded = createTextFile(dbDirConfig + File.separator + "pg_ident.conf",
425                                 DEFAULT_CONFIG_PG_IDENT);
426                 if (!identConfCreationSucceeded) {
427                         System.out.println("initialize(): createTextFile() failed.");
428                         return false;
429                 }
430
431                 return true;
432         }
433
434         /**
435          * @param path
436          * @param contents
437          * @return
438          */
439         private static boolean createTextFile(final String path, final String contents) {
440                 final File file = new File(path);
441                 final File parent = file.getParentFile();
442                 if (parent == null) {
443                         System.out.println("initialize(): getParentFile() failed.");
444                         return false;
445                 }
446
447                 parent.mkdirs();
448                 try {
449                         file.createNewFile();
450                 } catch (final IOException e) {
451                         // TODO Auto-generated catch block
452                         e.printStackTrace();
453                         return false;
454                 }
455
456                 FileOutputStream output = null;
457                 try {
458                         output = new FileOutputStream(file);
459                 } catch (final FileNotFoundException e) {
460                         // TODO Auto-generated catch block
461                         e.printStackTrace();
462                         return false;
463                 }
464
465                 try {
466                         output.write(contents.getBytes());
467                 } catch (final IOException e) {
468                         // TODO Auto-generated catch block
469                         e.printStackTrace();
470
471                         //TODO: Avoid the duplicate close() here.
472                         try {
473                                 output.close();
474                         } catch (IOException e1) {
475                                 // TODO Auto-generated catch block
476                                 e1.printStackTrace();
477                         }
478
479                         return false;
480                 }
481
482                 try {
483                         output.close();
484                 } catch (IOException e) {
485                         // TODO Auto-generated catch block
486                         e.printStackTrace();
487                 }
488
489                 return true;
490         }
491
492         /**
493          * @param document
494          * @return
495          */
496         protected boolean recreateDatabaseFromDocument() {
497                 // Check whether the database exists already.
498                 final String dbName = document.getConnectionDatabase();
499                 if (StringUtils.isEmpty(dbName)) {
500                         return false;
501                 }
502
503                 document.setConnectionDatabase(dbName);
504                 Connection connection = createConnection(true);
505                 if (connection != null) {
506                         // Connection to the database succeeded, so the database
507                         // exists already.
508                         try {
509                                 connection.close();
510                         } catch (final SQLException e) {
511                                 // TODO Auto-generated catch block
512                                 e.printStackTrace();
513                         }
514                         return false;
515                 }
516
517                 // Create the database:
518                 progress();
519                 document.setConnectionDatabase("");
520
521                 connection = createConnection(false);
522                 if (connection == null) {
523                         System.out.println("recreatedDatabase(): createConnection() failed, before creating the database.");
524                         return false;
525                 }
526
527                 final boolean dbCreated = createDatabase(connection, dbName);
528
529                 if (!dbCreated) {
530                         return false;
531                 }
532
533                 progress();
534
535                 // Check that we can connect:
536                 try {
537                         connection.close();
538                 } catch (final SQLException e) {
539                         // TODO Auto-generated catch block
540                         e.printStackTrace();
541                 }
542                 connection = null;
543
544                 document.setConnectionDatabase(dbName);
545                 connection = createConnection(false);
546                 if (connection == null) {
547                         System.out.println("recreatedDatabase(): createConnection() failed, after creating the database.");
548                         return false;
549                 }
550
551                 progress();
552
553                 // Create each table:
554                 final List<String> tables = document.getTableNames();
555                 for (final String tableName : tables) {
556
557                         // Create SQL to describe all fields in this table:
558                         final List<Field> fields = document.getTableFields(tableName);
559
560                         progress();
561                         final boolean tableCreationSucceeded = createTable(connection, document, tableName, fields);
562                         progress();
563                         if (!tableCreationSucceeded) {
564                                 // TODO: std::cerr << G_STRFUNC << ": CREATE TABLE failed with the newly-created database." <<
565                                 // std::endl;
566                                 return false;
567                         }
568                 }
569
570                 // Note that create_database() has already called add_standard_tables() and add_standard_groups(document).
571
572                 // Add groups from the document:
573                 progress();
574                 if (!addGroupsFromDocument(document)) {
575                         // TODO: std::cerr << G_STRFUNC << ": add_groups_from_document() failed." << std::endl;
576                         return false;
577                 }
578
579                 // Set table privileges, using the groups we just added:
580                 progress();
581                 if (!setTablePrivilegesGroupsFromDocument(document)) {
582                         // TODO: std::cerr << G_STRFUNC << ": set_table_privileges_groups_from_document() failed." << std::endl;
583                         return false;
584                 }
585
586                 for (final String tableName : tables) {
587                         // Add any example data to the table:
588                         progress();
589
590                         // try
591                         // {
592                         progress();
593                         final boolean tableInsertSucceeded = insertExampleData(connection, document, tableName);
594
595                         if (!tableInsertSucceeded) {
596                                 // TODO: std::cerr << G_STRFUNC << ": INSERT of example data failed with the newly-created database." <<
597                                 // std::endl;
598                                 return false;
599                         }
600                         // }
601                         // catch(final std::exception& ex)
602                         // {
603                         // std::cerr << G_STRFUNC << ": exception: " << ex.what() << std::endl;
604                         // HandleError(ex);
605                         // }
606
607                 } // for(tables)
608
609                 return true; // All tables created successfully.
610         }
611
612         /**
613          */
614         public Connection createConnection(boolean failureExpected) {
615                 //We don't just use SqlUtils.tryUsernameAndPassword() because it uses ComboPooledDataSource,
616                 //which does not automatically close its connections,
617                 //leading to errors because connections are already open.
618                 final SqlUtils.JdbcConnectionDetails details = SqlUtils.getJdbcConnectionDetails(document);
619                 if (details == null) {
620                         return null;
621                 }
622                 
623                 final Properties connectionProps = new Properties();
624                 connectionProps.put("user", this.username);
625                 connectionProps.put("password", this.password);
626
627                 Connection conn = null;
628                 try {
629                         //TODO: Remove these debug prints when we figure out why getConnection sometimes hangs. 
630                         //System.out.println("debug: SelfHosterPostgreSQL.createConnection(): before createConnection()");
631                         DriverManager.setLoginTimeout(10);
632                         conn = DriverManager.getConnection(details.jdbcURL, connectionProps);
633                         //System.out.println("debug: SelfHosterPostgreSQL.createConnection(): before createConnection()");
634                 } catch (final SQLException e) {
635                         if(!failureExpected) {
636                                 e.printStackTrace();
637                         }
638                         return null;
639                 }
640
641                 return conn;
642         }
643
644         /**
645          *
646          */
647         private void progress() {
648                 // TODO Auto-generated method stub
649
650         }
651
652         /**
653          * @param document
654          * @param tableName
655          * @param fields
656          * @return
657          */
658         private boolean createTable(final Connection connection, final Document document, final String tableName,
659                         final List<Field> fields) {
660                 boolean tableCreationSucceeded = false;
661
662                 /*
663                  * TODO: //Create the standard field too: //(We don't actually use this yet) if(std::find_if(fields.begin(),
664                  * fields.end(), predicate_FieldHasName<Field>(GLOM_STANDARD_FIELD_LOCK)) == fields.end()) { sharedptr<Field>
665                  * field = sharedptr<Field>::create(); field->set_name(GLOM_STANDARD_FIELD_LOCK);
666                  * field->set_glom_type(Field::TYPE_TEXT); fields.push_back(field); }
667                  */
668
669                 // Create SQL to describe all fields in this table:
670                 String sqlFields = "";
671                 for (final Field field : fields) {
672                         // Create SQL to describe this field:
673                         String sqlFieldDescription = quoteAndEscapeSqlId(field.getName()) + " " + field.getSqlType(Field.SqlDialect.POSTGRESQL);
674
675                         if (field.getPrimaryKey()) {
676                                 sqlFieldDescription += " NOT NULL  PRIMARY KEY";
677                         }
678
679                         // Append it:
680                         if (!StringUtils.isEmpty(sqlFields)) {
681                                 sqlFields += ", ";
682                         }
683
684                         sqlFields += sqlFieldDescription;
685                 }
686
687                 if (StringUtils.isEmpty(sqlFields)) {
688                         // TODO: std::cerr << G_STRFUNC << ": sql_fields is empty." << std::endl;
689                 }
690
691                 // Actually create the table
692                 final String query = "CREATE TABLE " + quoteAndEscapeSqlId(tableName) + " (" + sqlFields + ");";
693                 final Factory factory = new Factory(connection, getSqlDialect());
694                 factory.execute(query);
695                 tableCreationSucceeded = true;
696                 if (!tableCreationSucceeded) {
697                         System.out.println("recreatedDatabase(): CREATE TABLE() failed.");
698                 }
699
700                 return tableCreationSucceeded;
701         }
702
703         /**
704          * @param name
705          * @return
706          */
707         private static String quoteAndEscapeSqlId(final String name) {
708                 return quoteAndEscapeSqlId(name, SQLDialect.POSTGRES);
709         }
710
711         /**
712          * @return
713          */
714         private static boolean createDatabase(final Connection connection, final String databaseName) {
715                 final String query = "CREATE DATABASE " + quoteAndEscapeSqlId(databaseName);
716                 final Factory factory = new Factory(connection, SQLDialect.POSTGRES);
717
718                 factory.execute(query);
719
720                 return true;
721         }
722
723         /**
724          *
725          */
726         public boolean cleanup() {
727                 boolean result = true;
728
729                 // Stop the server:
730                 if ((document != null) && (document.getConnectionPort() != 0)) {
731                         final String dbDirData = getSelfHostingDataPath(false);
732
733                         // -D specifies the data directory.
734                         // -c config_file= specifies the configuration file
735                         // -k specifies a directory to use for the socket. This must be writable by us.
736                         // We use "-m fast" instead of the default "-m smart" because that waits for clients to disconnect (and
737                         // sometimes never succeeds).
738                         // TODO: Warn about connected clients on other computers? Warn those other users?
739                         // Make sure to use double quotes for the executable path, because the
740                         // CreateProcess() API used on Windows does not support single quotes.
741                         final String commandPath = getPathToPostgresExecutable("pg_ctl");
742                         if (StringUtils.isEmpty(commandPath)) {
743                                 System.out.println("cleanup(): getPathToPostgresExecutable(pg_ctl) failed.");
744                         } else {
745                                 final ProcessBuilder commandPostgresStop = new ProcessBuilder(commandPath,
746                                                 "-D" + shellQuote(dbDirData), "stop", "-m", "fast");
747                                 result = executeCommandLineAndWait(commandPostgresStop);
748                                 if (!result) {
749                                         System.out.println("cleanup(): Failed to stop the PostgreSQL server.");
750                                 }
751                         }
752
753                         document.setConnectionPort(0);
754                 }
755
756                 // Delete the files:
757                 final String selfhostingPath = getSelfHostingPath("", false);
758                 final File fileSelfHosting = new File(selfhostingPath);
759                 fileSelfHosting.delete();
760
761                 final String docPath = document.getFileURI();
762                 final File fileDoc = new File(docPath);
763                 fileDoc.delete();
764
765                 return result;
766         }
767         
768         @Override
769         public SQLDialect getSqlDialect() {
770                 return SQLDialect.POSTGRES;
771         }
772 }