Replace deprecated DOM.setElementAttribute().
[online-glom:gwt-glom.git] / src / test / java / org / glom / web / server / SelfHoster.java
1 /*
2  * Copyright (C) 2013 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.BufferedReader;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.net.ServerSocket;
28 import java.sql.Connection;
29 import java.sql.DriverManager;
30 import java.sql.SQLException;
31 import java.text.NumberFormat;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Properties;
36 import java.util.Map.Entry;
37
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;
48
49 import com.google.common.io.Files;
50
51 /**
52  * @author Murray Cumming <murrayc@openismus.com>
53  *
54  */
55 public class SelfHoster {
56
57         protected boolean selfHostingActive = false;
58         protected Document document = null;
59         protected String username = "";
60         protected String password = "";
61
62         /**
63          * 
64          */
65         public SelfHoster(final Document document) {
66                 super();
67                 this.document = document;
68         }
69         
70         public boolean createAndSelfHostFromExample() {
71
72                 if (!createAndSelfHostNewEmpty()) {
73                         // std::cerr << G_STRFUNC << ": test_create_and_selfhost_new_empty() failed." << std::endl;
74                         return false;
75                 }
76
77                 final boolean recreated = recreateDatabaseFromDocument(); /* TODO: Progress callback */
78                 if (!recreated) {
79                         if (!cleanup()) {
80                                 return false;
81                         }
82                 }
83
84                 return recreated;
85         }
86         
87         /**
88          * @return
89          */
90         protected boolean recreateDatabaseFromDocument() {
91                 // TODO Auto-generated method stub
92                 return false;
93         }
94
95         /**
96          * @param hostingMode
97          * @return
98          */
99         protected boolean createAndSelfHostNewEmpty() {
100                 // TODO Auto-generated method stub
101                 return false;
102         }
103         
104         /**
105          *
106          */
107         public boolean cleanup() {
108                 //Derived classes should implement this.
109                 return false;
110         }
111
112         public String getUsername() {
113                 return username;
114         }
115
116         public String getPassword() {
117                 return password;
118         }
119
120         /**
121          * @return
122          */
123         protected boolean getSelfHostingActive() {
124                 return selfHostingActive;
125         }
126         
127         protected boolean executeCommandLineAndWait(final ProcessBuilder command) {
128
129                 command.redirectErrorStream(true);
130
131                 // Run the first command, and wait for it to return:
132                 Process process = null;
133                 try {
134                         process = command.start();
135                 } catch (final IOException e) {
136                         // TODO Auto-generated catch block
137                         e.printStackTrace();
138                         return false;
139                 }
140
141                 // final InputStream stderr = process.getInputStream();
142                 // final InputStreamReader isr = new InputStreamReader(stderr);
143                 // final BufferedReader br = new BufferedReader(isr);
144                 // String output = "";
145                 // String line;
146                 /*
147                  * try { //TODO: readLine() can hang, waiting for an end of line that never comes. while ((line = br.readLine())
148                  * != null) { output += line + "\n"; } } catch (final IOException e1) { e1.printStackTrace(); return false; }
149                  */
150
151                 int result = 0;
152                 try {
153                         result = process.waitFor();
154                 } catch (final InterruptedException e) {
155                         // TODO Auto-generated catch block
156                         e.printStackTrace();
157                         return false;
158                 }
159
160                 if (result != 0) {
161                         System.out.println("executeCommandLineAndWait(): Command failed: " + command.command().toString());
162                         InputStream is = process.getInputStream();
163                 InputStreamReader isr = new InputStreamReader(is);
164                 BufferedReader br = new BufferedReader(isr);
165                 String line;
166                 try {
167                                 while ((line = br.readLine()) != null) {
168                                     System.out.println(line);
169                                 }
170                         } catch (IOException e) {
171                                 // TODO Auto-generated catch block
172                                 e.printStackTrace();
173                         }
174         
175                         return false;
176                 }
177
178                 return true;
179         }
180
181         protected boolean executeCommandLineAndWaitUntilSecondCommandReturnsSuccess(final ProcessBuilder command,
182                         final ProcessBuilder commandSecond, final String secondCommandSuccessText) {
183                 command.redirectErrorStream(true);
184
185                 // Run the first command, and do not wait for it to return:
186                 // Process process = null;
187                 try {
188                         // Process process =
189                         command.start();
190                 } catch (final IOException e) {
191                         // TODO Auto-generated catch block
192                         e.printStackTrace();
193                         return false;
194                 }
195
196                 // final InputStream stderr = process.getInputStream();
197                 // final InputStreamReader isr = new InputStreamReader(stderr);
198                 // final BufferedReader br = new BufferedReader(isr);
199
200                 /*
201                  * We do not wait, because this (postgres, for instance), does not return: final int result = process.waitFor();
202                  * if (result != 0) { // TODO: Warn. return false; }
203                  */
204
205                 // Now run the second command, usually to verify that the first command has really done its work:
206                 // We run this repeatedly until it succeeds, to show that the first command has finished.
207                 boolean result = false;
208                 while (true) {
209                         result = executeCommandLineAndWait(commandSecond);
210                         if (result) {
211                                 System.out.println("executeCommandLineAndWait(): second command succeeded.");
212                                 return true;
213                         } else {
214                                 try {
215                                         Thread.sleep(1000);
216                                 } catch (InterruptedException e) {
217                                         // TODO Auto-generated catch block
218                                         e.printStackTrace();
219                                         return false;
220                                 }
221
222                                 System.out.println("executeCommandLineAndWait(): Trying the second command again.");
223                         }
224                 }
225
226                 // Try to get the output:
227                 /*
228                  * if (!result) { String output = ""; /* String line; try { // TODO: readLine() can hang, waiting for an end of
229                  * line that never comes. while ((line = br.readLine()) != null) { output += line + "\n";
230                  * System.out.println(line); } } catch (final IOException e1) { // TODO Auto-generated catch block
231                  * e1.printStackTrace(); return false; }
232                  */
233
234                 // System.out.println("  Output of first command: " + output);
235                 // System.out.println("  first command: " + command.command().toString());
236                 // System.out.println("  second command: " + commandSecond.command().toString());
237                 // }
238         }
239
240         /**
241          * @param document
242          * @param tableName
243          * @return
244          */
245         protected boolean insertExampleData(final Connection connection, final Document document, final String tableName) {
246
247                 final Factory factory = new Factory(connection, getSqlDialect());
248                 final Table<Record> table = Factory.tableByName(tableName);
249
250                 final List<Map<String, DataItem>> exampleRows = document.getExampleRows(tableName);
251                 for (final Map<String, DataItem> row : exampleRows) {
252                         InsertSetStep<Record> insertStep = factory.insertInto(table);
253
254                         for (final Entry<String, DataItem> entry : row.entrySet()) {
255                                 final String fieldName = entry.getKey();
256                                 final DataItem value = entry.getValue();
257                                 if (value == null) {
258                                         continue;
259                                 }
260
261                                 final Field field = document.getField(tableName, fieldName);
262                                 if (field == null) {
263                                         continue;
264                                 }
265
266                                 final org.jooq.Field<Object> jooqField = Factory.fieldByName(field.getName());
267                                 if (jooqField == null) {
268                                         continue;
269                                 }
270
271                                 final Object fieldValue = value.getValue(field.getGlomType());
272                                 insertStep = insertStep.set(jooqField, fieldValue);
273                         }
274
275                         if (!(insertStep instanceof InsertResultStep<?>)) {
276                                 continue;
277                         }
278
279                         // We suppress the warning because we _do_ check the cast above.
280                         @SuppressWarnings("unchecked")
281                         final InsertResultStep<Record> insertResultStep = (InsertResultStep<Record>) insertStep;
282
283                         try {
284                                 insertResultStep.fetchOne();
285                         } catch (final DataAccessException e) {
286                                 System.out.println("createAndSelfHostNewEmpty(): insertResultStep failed.");
287                                 e.printStackTrace();
288                                 return false;
289                         }
290                         // TODO: Check that it worked.
291                 }
292
293                 return true;
294         }
295
296
297         /**
298          * @param document
299          * @return
300          */
301         protected boolean addGroupsFromDocument(final Document document) {
302                 // TODO Auto-generated method stub
303                 return true;
304         }
305
306         /**
307          * @param document
308          * @return
309          */
310         protected boolean setTablePrivilegesGroupsFromDocument(final Document document) {
311                 // TODO Auto-generated method stub
312                 return true;
313         }
314         
315         /**
316          * @param dbDir
317          * @return
318          */
319         protected static boolean fileExists(final String filePath) {
320                 final File file = new File(filePath);
321                 return file.exists();
322         }
323
324         /**
325          * @param start
326          * @param end
327          * @return
328          */
329         protected static int discoverFirstFreePort(final int start, final int end) {
330                 for (int port = start; port <= end; ++port) {
331                         try {
332                                 final ServerSocket socket = new ServerSocket(port);
333         
334                                 // If the instantiation succeeded then the port was free:
335                                 final int result = socket.getLocalPort(); // This must equal port.
336                                 socket.close();
337                                 return result;
338                         } catch (final IOException ex) {
339                                 continue; // try next port
340                         }
341                 }
342         
343                 return 0;
344         }
345         
346         /**
347          * @param path
348          * @return
349          */
350         protected static boolean fileExistsAndIsExecutable(String path) {
351                 final File file = new File(path);
352                 if (!file.exists()) {
353                         return false;
354                 }
355
356                 if (!file.canExecute()) {
357                         return false;
358                 }
359
360                 return true;
361         }
362         
363         /**
364          * @param portNumber
365          * @return
366          */
367         protected String portNumberAsText(final int portNumber) {
368                 final NumberFormat format = NumberFormat.getInstance(Locale.US);
369                 format.setGroupingUsed(false); // TODO: Does this change it system-wide?
370                 final String portAsText = format.format(portNumber);
371                 return portAsText;
372         }
373         
374         /**
375          */
376         public Connection createConnection(boolean failureExpected) {
377                 //We don't just use SqlUtils.tryUsernameAndPassword() because it uses ComboPooledDataSource,
378                 //which does not automatically close its connections,
379                 //leading to errors because connections are already open.
380                 final SqlUtils.JdbcConnectionDetails details = SqlUtils.getJdbcConnectionDetails(document);
381                 if (details == null) {
382                         return null;
383                 }
384                 
385                 final Properties connectionProps = new Properties();
386                 connectionProps.put("user", this.username);
387                 connectionProps.put("password", this.password);
388
389                 Connection conn = null;
390                 try {
391                         //TODO: Remove these debug prints when we figure out why getConnection sometimes hangs. 
392                         //System.out.println("debug: SelfHosterPostgreSQL.createConnection(): before createConnection()");
393                         DriverManager.setLoginTimeout(10);
394                         conn = DriverManager.getConnection(details.jdbcURL, connectionProps);
395                         //System.out.println("debug: createConnection(): before createConnection()");
396                 } catch (final SQLException e) {
397                         if(!failureExpected) {
398                                 e.printStackTrace();
399                         }
400                         return null;
401                 }
402
403                 return conn;
404         }
405
406         /**
407          * @param dbDirData
408          * @return
409          */
410         protected String shellQuote(final String str) {
411                 // TODO: If we add the quotes then they seem to be used as part of the path, though that is not a problem with
412                 // the normal command line.
413                 return str;
414
415                 // TODO: Escape.
416                 // return "'" + str + "'";
417         }
418         
419         /**
420          * @return The temporary directory where the file was saved.
421          */
422         protected File saveDocumentCopy(Document.HostingMode hostingMode) {
423                 // Save a copy, specifying the path to file in a directory:
424                 // For instance, /tmp/testglom/testglom.glom");
425                 final String tempFilename = "testglom";
426                 final File tempFolder = Files.createTempDir();
427                 final File tempDir = new File(tempFolder, tempFilename);
428
429                 final String tempDirPath = tempDir.getPath();
430                 final String tempFilePath = tempDirPath + File.separator + tempFilename;
431                 final File file = new File(tempFilePath);
432
433                 // Make sure that the file does not exist yet:
434                 {
435                         tempDir.delete();
436                 }
437
438                 // Save the example as a real file:
439                 document.setFileURI(file.getPath());
440
441                 document.setHostingMode(hostingMode);
442                 document.setIsExampleFile(false);
443                 final boolean saved = document.save();
444                 if (!saved) {
445                         System.out.println("createAndSelfHostNewEmpty(): Document.save() failed.");
446                         return null; // TODO: Delete the directory.
447                 }
448                 return tempDir;
449         }
450
451         /**
452          * @return
453          */
454         public SQLDialect getSqlDialect() {
455                 //This must be overriden by the derived classes.
456                 return null;
457         }
458
459         /**
460          * @param name
461          * @return
462          */
463         public static String quoteAndEscapeSqlId(final String name, final SQLDialect sqlDialect) {
464                 //final Factory factory = new Factory(connection, getSqlDialect());
465                 final org.jooq.Name jooqName = Factory.name(name);
466                 if(jooqName == null) {
467                         return null;
468                 }
469
470                 final Factory factory = new Factory(sqlDialect);
471                 return factory.render(jooqName);
472         }
473
474 }