1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.server.web; |
7 | |
8 | import java.io.File; |
9 | import java.io.FileInputStream; |
10 | import java.io.IOException; |
11 | import java.io.InputStream; |
12 | import java.io.OutputStream; |
13 | import java.net.ServerSocket; |
14 | import java.net.Socket; |
15 | import java.sql.Connection; |
16 | import java.sql.SQLException; |
17 | import java.text.SimpleDateFormat; |
18 | import java.util.ArrayList; |
19 | import java.util.Collections; |
20 | import java.util.HashMap; |
21 | import java.util.HashSet; |
22 | import java.util.Locale; |
23 | import java.util.Map; |
24 | import java.util.Map.Entry; |
25 | import java.util.Properties; |
26 | import java.util.Set; |
27 | import java.util.TimeZone; |
28 | |
29 | import org.h2.engine.Constants; |
30 | import org.h2.engine.SysProperties; |
31 | import org.h2.message.DbException; |
32 | import org.h2.server.Service; |
33 | import org.h2.server.ShutdownHandler; |
34 | import org.h2.store.fs.FileUtils; |
35 | import org.h2.util.IOUtils; |
36 | import org.h2.util.JdbcUtils; |
37 | import org.h2.util.MathUtils; |
38 | import org.h2.util.NetUtils; |
39 | import org.h2.util.New; |
40 | import org.h2.util.SortedProperties; |
41 | import org.h2.util.StringUtils; |
42 | import org.h2.util.Tool; |
43 | import org.h2.util.Utils; |
44 | |
45 | /** |
46 | * The web server is a simple standalone HTTP server that implements the H2 |
47 | * Console application. It is not optimized for performance. |
48 | */ |
49 | public class WebServer implements Service { |
50 | |
51 | static final String TRANSFER = "transfer"; |
52 | |
53 | static final String[][] LANGUAGES = { |
54 | { "cs", "\u010ce\u0161tina" }, |
55 | { "de", "Deutsch" }, |
56 | { "en", "English" }, |
57 | { "es", "Espa\u00f1ol" }, |
58 | { "fr", "Fran\u00e7ais" }, |
59 | { "hu", "Magyar"}, |
60 | { "ko", "\ud55c\uad6d\uc5b4"}, |
61 | { "in", "Indonesia"}, |
62 | { "it", "Italiano"}, |
63 | { "ja", "\u65e5\u672c\u8a9e"}, |
64 | { "nl", "Nederlands"}, |
65 | { "pl", "Polski"}, |
66 | { "pt_BR", "Portugu\u00eas (Brasil)"}, |
67 | { "pt_PT", "Portugu\u00eas (Europeu)"}, |
68 | { "ru", "\u0440\u0443\u0441\u0441\u043a\u0438\u0439"}, |
69 | { "sk", "Slovensky"}, |
70 | { "tr", "T\u00fcrk\u00e7e"}, |
71 | { "uk", "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430"}, |
72 | { "zh_CN", "\u4e2d\u6587 (\u7b80\u4f53)"}, |
73 | { "zh_TW", "\u4e2d\u6587 (\u7e41\u9ad4)"}, |
74 | }; |
75 | |
76 | private static final String COMMAND_HISTORY = "commandHistory"; |
77 | |
78 | private static final String DEFAULT_LANGUAGE = "en"; |
79 | |
80 | private static final String[] GENERIC = { |
81 | "Generic JNDI Data Source|javax.naming.InitialContext|" + |
82 | "java:comp/env/jdbc/Test|sa", |
83 | "Generic Firebird Server|org.firebirdsql.jdbc.FBDriver|" + |
84 | "jdbc:firebirdsql:localhost:c:/temp/firebird/test|sysdba", |
85 | "Generic SQLite|org.sqlite.JDBC|" + |
86 | "jdbc:sqlite:test|sa", |
87 | "Generic DB2|COM.ibm.db2.jdbc.net.DB2Driver|" + |
88 | "jdbc:db2://localhost/test|" , |
89 | "Generic Oracle|oracle.jdbc.driver.OracleDriver|" + |
90 | "jdbc:oracle:thin:@localhost:1521:XE|sa" , |
91 | "Generic MS SQL Server 2000|com.microsoft.jdbc.sqlserver.SQLServerDriver|" + |
92 | "jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=sqlexpress|sa", |
93 | "Generic MS SQL Server 2005|com.microsoft.sqlserver.jdbc.SQLServerDriver|" + |
94 | "jdbc:sqlserver://localhost;DatabaseName=test|sa", |
95 | "Generic PostgreSQL|org.postgresql.Driver|" + |
96 | "jdbc:postgresql:test|" , |
97 | "Generic MySQL|com.mysql.jdbc.Driver|" + |
98 | "jdbc:mysql://localhost:3306/test|" , |
99 | "Generic HSQLDB|org.hsqldb.jdbcDriver|" + |
100 | "jdbc:hsqldb:test;hsqldb.default_table_type=cached|sa" , |
101 | "Generic Derby (Server)|org.apache.derby.jdbc.ClientDriver|" + |
102 | "jdbc:derby://localhost:1527/test;create=true|sa", |
103 | "Generic Derby (Embedded)|org.apache.derby.jdbc.EmbeddedDriver|" + |
104 | "jdbc:derby:test;create=true|sa", |
105 | "Generic H2 (Server)|org.h2.Driver|" + |
106 | "jdbc:h2:tcp://localhost/~/test|sa", |
107 | // this will be listed on top for new installations |
108 | "Generic H2 (Embedded)|org.h2.Driver|" + |
109 | "jdbc:h2:~/test|sa", |
110 | }; |
111 | |
112 | private static int ticker; |
113 | |
114 | /** |
115 | * The session timeout (the default is 30 minutes). |
116 | */ |
117 | private static final long SESSION_TIMEOUT = SysProperties.CONSOLE_TIMEOUT; |
118 | |
119 | // public static void main(String... args) throws IOException { |
120 | // String s = IOUtils.readStringAndClose(new java.io.FileReader( |
121 | // // "src/main/org/h2/server/web/res/_text_cs.prop"), -1); |
122 | // "src/main/org/h2/res/_messages_cs.prop"), -1); |
123 | // System.out.println(StringUtils.javaEncode("...")); |
124 | // String[] list = Locale.getISOLanguages(); |
125 | // for (int i = 0; i < list.length; i++) { |
126 | // System.out.print(list[i] + " "); |
127 | // } |
128 | // System.out.println(); |
129 | // String l = "de"; |
130 | // String lang = new java.util.Locale(l). |
131 | // getDisplayLanguage(new java.util.Locale(l)); |
132 | // System.out.println(new java.util.Locale(l).getDisplayLanguage()); |
133 | // System.out.println(lang); |
134 | // java.util.Locale.CHINESE.getDisplayLanguage(java.util.Locale.CHINESE); |
135 | // for (int i = 0; i < lang.length(); i++) { |
136 | // System.out.println(Integer.toHexString(lang.charAt(i)) + " "); |
137 | // } |
138 | // } |
139 | |
140 | // private URLClassLoader urlClassLoader; |
141 | private int port; |
142 | private boolean allowOthers; |
143 | private boolean isDaemon; |
144 | private final Set<WebThread> running = |
145 | Collections.synchronizedSet(new HashSet<WebThread>()); |
146 | private boolean ssl; |
147 | private final HashMap<String, ConnectionInfo> connInfoMap = New.hashMap(); |
148 | |
149 | private long lastTimeoutCheck; |
150 | private final HashMap<String, WebSession> sessions = New.hashMap(); |
151 | private final HashSet<String> languages = New.hashSet(); |
152 | private String startDateTime; |
153 | private ServerSocket serverSocket; |
154 | private String url; |
155 | private ShutdownHandler shutdownHandler; |
156 | private Thread listenerThread; |
157 | private boolean ifExists; |
158 | private boolean trace; |
159 | private TranslateThread translateThread; |
160 | private boolean allowChunked = true; |
161 | private String serverPropertiesDir = Constants.SERVER_PROPERTIES_DIR; |
162 | // null means the history is not allowed to be stored |
163 | private String commandHistoryString; |
164 | |
165 | /** |
166 | * Read the given file from the file system or from the resources. |
167 | * |
168 | * @param file the file name |
169 | * @return the data |
170 | */ |
171 | byte[] getFile(String file) throws IOException { |
172 | trace("getFile <" + file + ">"); |
173 | if (file.startsWith(TRANSFER + "/") && new File(TRANSFER).exists()) { |
174 | file = file.substring(TRANSFER.length() + 1); |
175 | if (!isSimpleName(file)) { |
176 | return null; |
177 | } |
178 | File f = new File(TRANSFER, file); |
179 | if (!f.exists()) { |
180 | return null; |
181 | } |
182 | return IOUtils.readBytesAndClose(new FileInputStream(f), -1); |
183 | } |
184 | byte[] data = Utils.getResource("/org/h2/server/web/res/" + file); |
185 | if (data == null) { |
186 | trace(" null"); |
187 | } else { |
188 | trace(" size=" + data.length); |
189 | } |
190 | return data; |
191 | } |
192 | |
193 | /** |
194 | * Check if this is a simple name (only contains '.', '-', '_', letters, or |
195 | * digits). |
196 | * |
197 | * @param s the string |
198 | * @return true if it's a simple name |
199 | */ |
200 | static boolean isSimpleName(String s) { |
201 | for (char c : s.toCharArray()) { |
202 | if (c != '.' && c != '_' && c != '-' && !Character.isLetterOrDigit(c)) { |
203 | return false; |
204 | } |
205 | } |
206 | return true; |
207 | } |
208 | |
209 | /** |
210 | * Remove this web thread from the set of running threads. |
211 | * |
212 | * @param t the thread to remove |
213 | */ |
214 | synchronized void remove(WebThread t) { |
215 | running.remove(t); |
216 | } |
217 | |
218 | private static String generateSessionId() { |
219 | byte[] buff = MathUtils.secureRandomBytes(16); |
220 | return StringUtils.convertBytesToHex(buff); |
221 | } |
222 | |
223 | /** |
224 | * Get the web session object for the given session id. |
225 | * |
226 | * @param sessionId the session id |
227 | * @return the web session or null |
228 | */ |
229 | WebSession getSession(String sessionId) { |
230 | long now = System.currentTimeMillis(); |
231 | if (lastTimeoutCheck + SESSION_TIMEOUT < now) { |
232 | for (String id : New.arrayList(sessions.keySet())) { |
233 | WebSession session = sessions.get(id); |
234 | if (session.lastAccess + SESSION_TIMEOUT < now) { |
235 | trace("timeout for " + id); |
236 | sessions.remove(id); |
237 | } |
238 | } |
239 | lastTimeoutCheck = now; |
240 | } |
241 | WebSession session = sessions.get(sessionId); |
242 | if (session != null) { |
243 | session.lastAccess = System.currentTimeMillis(); |
244 | } |
245 | return session; |
246 | } |
247 | |
248 | /** |
249 | * Create a new web session id and object. |
250 | * |
251 | * @param hostAddr the host address |
252 | * @return the web session object |
253 | */ |
254 | WebSession createNewSession(String hostAddr) { |
255 | String newId; |
256 | do { |
257 | newId = generateSessionId(); |
258 | } while (sessions.get(newId) != null); |
259 | WebSession session = new WebSession(this); |
260 | session.lastAccess = System.currentTimeMillis(); |
261 | session.put("sessionId", newId); |
262 | session.put("ip", hostAddr); |
263 | session.put("language", DEFAULT_LANGUAGE); |
264 | session.put("frame-border", "0"); |
265 | session.put("frameset-border", "4"); |
266 | sessions.put(newId, session); |
267 | // always read the english translation, |
268 | // so that untranslated text appears at least in english |
269 | readTranslations(session, DEFAULT_LANGUAGE); |
270 | return getSession(newId); |
271 | } |
272 | |
273 | String getStartDateTime() { |
274 | if (startDateTime == null) { |
275 | SimpleDateFormat format = new SimpleDateFormat( |
276 | "EEE, d MMM yyyy HH:mm:ss z", new Locale("en", "")); |
277 | format.setTimeZone(TimeZone.getTimeZone("GMT")); |
278 | startDateTime = format.format(System.currentTimeMillis()); |
279 | } |
280 | return startDateTime; |
281 | } |
282 | |
283 | @Override |
284 | public void init(String... args) { |
285 | // set the serverPropertiesDir, because it's used in loadProperties() |
286 | for (int i = 0; args != null && i < args.length; i++) { |
287 | if ("-properties".equals(args[i])) { |
288 | serverPropertiesDir = args[++i]; |
289 | } |
290 | } |
291 | Properties prop = loadProperties(); |
292 | port = SortedProperties.getIntProperty(prop, |
293 | "webPort", Constants.DEFAULT_HTTP_PORT); |
294 | ssl = SortedProperties.getBooleanProperty(prop, |
295 | "webSSL", false); |
296 | allowOthers = SortedProperties.getBooleanProperty(prop, |
297 | "webAllowOthers", false); |
298 | commandHistoryString = prop.getProperty(COMMAND_HISTORY); |
299 | for (int i = 0; args != null && i < args.length; i++) { |
300 | String a = args[i]; |
301 | if (Tool.isOption(a, "-webPort")) { |
302 | port = Integer.decode(args[++i]); |
303 | } else if (Tool.isOption(a, "-webSSL")) { |
304 | ssl = true; |
305 | } else if (Tool.isOption(a, "-webAllowOthers")) { |
306 | allowOthers = true; |
307 | } else if (Tool.isOption(a, "-webDaemon")) { |
308 | isDaemon = true; |
309 | } else if (Tool.isOption(a, "-baseDir")) { |
310 | String baseDir = args[++i]; |
311 | SysProperties.setBaseDir(baseDir); |
312 | } else if (Tool.isOption(a, "-ifExists")) { |
313 | ifExists = true; |
314 | } else if (Tool.isOption(a, "-properties")) { |
315 | // already set |
316 | i++; |
317 | } else if (Tool.isOption(a, "-trace")) { |
318 | trace = true; |
319 | } |
320 | } |
321 | // if (driverList != null) { |
322 | // try { |
323 | // String[] drivers = |
324 | // StringUtils.arraySplit(driverList, ',', false); |
325 | // URL[] urls = new URL[drivers.length]; |
326 | // for(int i=0; i<drivers.length; i++) { |
327 | // urls[i] = new URL(drivers[i]); |
328 | // } |
329 | // urlClassLoader = URLClassLoader.newInstance(urls); |
330 | // } catch (MalformedURLException e) { |
331 | // TraceSystem.traceThrowable(e); |
332 | // } |
333 | // } |
334 | for (String[] lang : LANGUAGES) { |
335 | languages.add(lang[0]); |
336 | } |
337 | updateURL(); |
338 | } |
339 | |
340 | @Override |
341 | public String getURL() { |
342 | updateURL(); |
343 | return url; |
344 | } |
345 | |
346 | private void updateURL() { |
347 | try { |
348 | url = (ssl ? "https" : "http") + "://" + |
349 | NetUtils.getLocalAddress() + ":" + port; |
350 | } catch (NoClassDefFoundError e) { |
351 | // Google App Engine does not allow java.net.InetAddress |
352 | } |
353 | } |
354 | |
355 | @Override |
356 | public void start() { |
357 | serverSocket = NetUtils.createServerSocket(port, ssl); |
358 | port = serverSocket.getLocalPort(); |
359 | updateURL(); |
360 | } |
361 | |
362 | @Override |
363 | public void listen() { |
364 | this.listenerThread = Thread.currentThread(); |
365 | try { |
366 | while (serverSocket != null) { |
367 | Socket s = serverSocket.accept(); |
368 | WebThread c = new WebThread(s, this); |
369 | running.add(c); |
370 | c.start(); |
371 | } |
372 | } catch (Exception e) { |
373 | trace(e.toString()); |
374 | } |
375 | } |
376 | |
377 | @Override |
378 | public boolean isRunning(boolean traceError) { |
379 | if (serverSocket == null) { |
380 | return false; |
381 | } |
382 | try { |
383 | Socket s = NetUtils.createLoopbackSocket(port, ssl); |
384 | s.close(); |
385 | return true; |
386 | } catch (Exception e) { |
387 | if (traceError) { |
388 | traceError(e); |
389 | } |
390 | return false; |
391 | } |
392 | } |
393 | |
394 | public boolean isStopped() { |
395 | return serverSocket == null; |
396 | } |
397 | |
398 | @Override |
399 | public void stop() { |
400 | if (serverSocket != null) { |
401 | try { |
402 | serverSocket.close(); |
403 | } catch (IOException e) { |
404 | traceError(e); |
405 | } |
406 | serverSocket = null; |
407 | } |
408 | if (listenerThread != null) { |
409 | try { |
410 | listenerThread.join(1000); |
411 | } catch (InterruptedException e) { |
412 | DbException.traceThrowable(e); |
413 | } |
414 | } |
415 | // TODO server: using a boolean 'now' argument? a timeout? |
416 | for (WebSession session : New.arrayList(sessions.values())) { |
417 | session.close(); |
418 | } |
419 | for (WebThread c : New.arrayList(running)) { |
420 | try { |
421 | c.stopNow(); |
422 | c.join(100); |
423 | } catch (Exception e) { |
424 | traceError(e); |
425 | } |
426 | } |
427 | } |
428 | |
429 | /** |
430 | * Write trace information if trace is enabled. |
431 | * |
432 | * @param s the message to write |
433 | */ |
434 | void trace(String s) { |
435 | if (trace) { |
436 | System.out.println(s); |
437 | } |
438 | } |
439 | |
440 | /** |
441 | * Write the stack trace if trace is enabled. |
442 | * |
443 | * @param e the exception |
444 | */ |
445 | void traceError(Throwable e) { |
446 | if (trace) { |
447 | e.printStackTrace(); |
448 | } |
449 | } |
450 | |
451 | /** |
452 | * Check if this language is supported / translated. |
453 | * |
454 | * @param language the language |
455 | * @return true if a translation is available |
456 | */ |
457 | boolean supportsLanguage(String language) { |
458 | return languages.contains(language); |
459 | } |
460 | |
461 | /** |
462 | * Read the translation for this language and save them in the 'text' |
463 | * property of this session. |
464 | * |
465 | * @param session the session |
466 | * @param language the language |
467 | */ |
468 | void readTranslations(WebSession session, String language) { |
469 | Properties text = new Properties(); |
470 | try { |
471 | trace("translation: "+language); |
472 | byte[] trans = getFile("_text_"+language+".prop"); |
473 | trace(" "+new String(trans)); |
474 | text = SortedProperties.fromLines(new String(trans, Constants.UTF8)); |
475 | // remove starting # (if not translated yet) |
476 | for (Entry<Object, Object> entry : text.entrySet()) { |
477 | String value = (String) entry.getValue(); |
478 | if (value.startsWith("#")) { |
479 | entry.setValue(value.substring(1)); |
480 | } |
481 | } |
482 | } catch (IOException e) { |
483 | DbException.traceThrowable(e); |
484 | } |
485 | session.put("text", new HashMap<Object, Object>(text)); |
486 | } |
487 | |
488 | ArrayList<HashMap<String, Object>> getSessions() { |
489 | ArrayList<HashMap<String, Object>> list = New.arrayList(); |
490 | for (WebSession s : sessions.values()) { |
491 | list.add(s.getInfo()); |
492 | } |
493 | return list; |
494 | } |
495 | |
496 | @Override |
497 | public String getType() { |
498 | return "Web Console"; |
499 | } |
500 | |
501 | @Override |
502 | public String getName() { |
503 | return "H2 Console Server"; |
504 | } |
505 | |
506 | void setAllowOthers(boolean b) { |
507 | allowOthers = b; |
508 | } |
509 | |
510 | @Override |
511 | public boolean getAllowOthers() { |
512 | return allowOthers; |
513 | } |
514 | |
515 | void setSSL(boolean b) { |
516 | ssl = b; |
517 | } |
518 | |
519 | void setPort(int port) { |
520 | this.port = port; |
521 | } |
522 | |
523 | boolean getSSL() { |
524 | return ssl; |
525 | } |
526 | |
527 | @Override |
528 | public int getPort() { |
529 | return port; |
530 | } |
531 | |
532 | public boolean isCommandHistoryAllowed() { |
533 | return commandHistoryString != null; |
534 | } |
535 | |
536 | public void setCommandHistoryAllowed(boolean allowed) { |
537 | if (allowed) { |
538 | if (commandHistoryString == null) { |
539 | commandHistoryString = ""; |
540 | } |
541 | } else { |
542 | commandHistoryString = null; |
543 | } |
544 | } |
545 | |
546 | public ArrayList<String> getCommandHistoryList() { |
547 | ArrayList<String> result = New.arrayList(); |
548 | if (commandHistoryString == null) { |
549 | return result; |
550 | } |
551 | |
552 | // Split the commandHistoryString on non-escaped semicolons |
553 | // and unescape it. |
554 | StringBuilder sb = new StringBuilder(); |
555 | for (int end = 0;; end++) { |
556 | if (end == commandHistoryString.length() || |
557 | commandHistoryString.charAt(end) == ';') { |
558 | if (sb.length() > 0) { |
559 | result.add(sb.toString()); |
560 | sb.delete(0, sb.length()); |
561 | } |
562 | if (end == commandHistoryString.length()) { |
563 | break; |
564 | } |
565 | } else if (commandHistoryString.charAt(end) == '\\' && |
566 | end < commandHistoryString.length() - 1) { |
567 | sb.append(commandHistoryString.charAt(++end)); |
568 | } else { |
569 | sb.append(commandHistoryString.charAt(end)); |
570 | } |
571 | } |
572 | return result; |
573 | } |
574 | |
575 | /** |
576 | * Save the command history to the properties file. |
577 | * |
578 | * @param commandHistory the history |
579 | */ |
580 | public void saveCommandHistoryList(ArrayList<String> commandHistory) { |
581 | StringBuilder sb = new StringBuilder(); |
582 | for (String s : commandHistory) { |
583 | if (sb.length() > 0) { |
584 | sb.append(';'); |
585 | } |
586 | sb.append(s.replace("\\", "\\\\").replace(";", "\\;")); |
587 | } |
588 | commandHistoryString = sb.toString(); |
589 | saveProperties(null); |
590 | } |
591 | |
592 | /** |
593 | * Get the connection information for this setting. |
594 | * |
595 | * @param name the setting name |
596 | * @return the connection information |
597 | */ |
598 | ConnectionInfo getSetting(String name) { |
599 | return connInfoMap.get(name); |
600 | } |
601 | |
602 | /** |
603 | * Update a connection information setting. |
604 | * |
605 | * @param info the connection information |
606 | */ |
607 | void updateSetting(ConnectionInfo info) { |
608 | connInfoMap.put(info.name, info); |
609 | info.lastAccess = ticker++; |
610 | } |
611 | |
612 | /** |
613 | * Remove a connection information setting from the list |
614 | * |
615 | * @param name the setting to remove |
616 | */ |
617 | void removeSetting(String name) { |
618 | connInfoMap.remove(name); |
619 | } |
620 | |
621 | private Properties loadProperties() { |
622 | try { |
623 | if ("null".equals(serverPropertiesDir)) { |
624 | return new Properties(); |
625 | } |
626 | return SortedProperties.loadProperties( |
627 | serverPropertiesDir + "/" + Constants.SERVER_PROPERTIES_NAME); |
628 | } catch (Exception e) { |
629 | DbException.traceThrowable(e); |
630 | return new Properties(); |
631 | } |
632 | } |
633 | |
634 | /** |
635 | * Get the list of connection information setting names. |
636 | * |
637 | * @return the connection info names |
638 | */ |
639 | String[] getSettingNames() { |
640 | ArrayList<ConnectionInfo> list = getSettings(); |
641 | String[] names = new String[list.size()]; |
642 | for (int i = 0; i < list.size(); i++) { |
643 | names[i] = list.get(i).name; |
644 | } |
645 | return names; |
646 | } |
647 | |
648 | /** |
649 | * Get the list of connection info objects. |
650 | * |
651 | * @return the list |
652 | */ |
653 | synchronized ArrayList<ConnectionInfo> getSettings() { |
654 | ArrayList<ConnectionInfo> settings = New.arrayList(); |
655 | if (connInfoMap.size() == 0) { |
656 | Properties prop = loadProperties(); |
657 | if (prop.size() == 0) { |
658 | for (String gen : GENERIC) { |
659 | ConnectionInfo info = new ConnectionInfo(gen); |
660 | settings.add(info); |
661 | updateSetting(info); |
662 | } |
663 | } else { |
664 | for (int i = 0;; i++) { |
665 | String data = prop.getProperty(String.valueOf(i)); |
666 | if (data == null) { |
667 | break; |
668 | } |
669 | ConnectionInfo info = new ConnectionInfo(data); |
670 | settings.add(info); |
671 | updateSetting(info); |
672 | } |
673 | } |
674 | } else { |
675 | settings.addAll(connInfoMap.values()); |
676 | } |
677 | Collections.sort(settings); |
678 | return settings; |
679 | } |
680 | |
681 | /** |
682 | * Save the settings to the properties file. |
683 | * |
684 | * @param prop null or the properties webPort, webAllowOthers, and webSSL |
685 | */ |
686 | synchronized void saveProperties(Properties prop) { |
687 | try { |
688 | if (prop == null) { |
689 | Properties old = loadProperties(); |
690 | prop = new SortedProperties(); |
691 | prop.setProperty("webPort", |
692 | "" + SortedProperties.getIntProperty(old, |
693 | "webPort", port)); |
694 | prop.setProperty("webAllowOthers", |
695 | "" + SortedProperties.getBooleanProperty(old, |
696 | "webAllowOthers", allowOthers)); |
697 | prop.setProperty("webSSL", |
698 | "" + SortedProperties.getBooleanProperty(old, |
699 | "webSSL", ssl)); |
700 | if (commandHistoryString != null) { |
701 | prop.setProperty(COMMAND_HISTORY, commandHistoryString); |
702 | } |
703 | } |
704 | ArrayList<ConnectionInfo> settings = getSettings(); |
705 | int len = settings.size(); |
706 | for (int i = 0; i < len; i++) { |
707 | ConnectionInfo info = settings.get(i); |
708 | if (info != null) { |
709 | prop.setProperty(String.valueOf(len - i - 1), info.getString()); |
710 | } |
711 | } |
712 | if (!"null".equals(serverPropertiesDir)) { |
713 | OutputStream out = FileUtils.newOutputStream( |
714 | serverPropertiesDir + "/" + Constants.SERVER_PROPERTIES_NAME, false); |
715 | prop.store(out, "H2 Server Properties"); |
716 | out.close(); |
717 | } |
718 | } catch (Exception e) { |
719 | DbException.traceThrowable(e); |
720 | } |
721 | } |
722 | |
723 | /** |
724 | * Open a database connection. |
725 | * |
726 | * @param driver the driver class name |
727 | * @param databaseUrl the database URL |
728 | * @param user the user name |
729 | * @param password the password |
730 | * @return the database connection |
731 | */ |
732 | Connection getConnection(String driver, String databaseUrl, String user, |
733 | String password) throws SQLException { |
734 | driver = driver.trim(); |
735 | databaseUrl = databaseUrl.trim(); |
736 | org.h2.Driver.load(); |
737 | Properties p = new Properties(); |
738 | p.setProperty("user", user.trim()); |
739 | // do not trim the password, otherwise an |
740 | // encrypted H2 database with empty user password doesn't work |
741 | p.setProperty("password", password); |
742 | if (databaseUrl.startsWith("jdbc:h2:")) { |
743 | if (ifExists) { |
744 | databaseUrl += ";IFEXISTS=TRUE"; |
745 | } |
746 | // PostgreSQL would throw a NullPointerException |
747 | // if it is loaded before the H2 driver |
748 | // because it can't deal with non-String objects in the connection |
749 | // Properties |
750 | return org.h2.Driver.load().connect(databaseUrl, p); |
751 | } |
752 | // try { |
753 | // Driver dr = (Driver) urlClassLoader. |
754 | // loadClass(driver).newInstance(); |
755 | // return dr.connect(url, p); |
756 | // } catch(ClassNotFoundException e2) { |
757 | // throw e2; |
758 | // } |
759 | return JdbcUtils.getConnection(driver, databaseUrl, p); |
760 | } |
761 | |
762 | /** |
763 | * Shut down the web server. |
764 | */ |
765 | void shutdown() { |
766 | if (shutdownHandler != null) { |
767 | shutdownHandler.shutdown(); |
768 | } |
769 | } |
770 | |
771 | public void setShutdownHandler(ShutdownHandler shutdownHandler) { |
772 | this.shutdownHandler = shutdownHandler; |
773 | } |
774 | |
775 | /** |
776 | * Create a session with a given connection. |
777 | * |
778 | * @param conn the connection |
779 | * @return the URL of the web site to access this connection |
780 | */ |
781 | public String addSession(Connection conn) throws SQLException { |
782 | WebSession session = createNewSession("local"); |
783 | session.setShutdownServerOnDisconnect(); |
784 | session.setConnection(conn); |
785 | session.put("url", conn.getMetaData().getURL()); |
786 | String s = (String) session.get("sessionId"); |
787 | return url + "/frame.jsp?jsessionid=" + s; |
788 | } |
789 | |
790 | /** |
791 | * The translate thread reads and writes the file translation.properties |
792 | * once a second. |
793 | */ |
794 | private class TranslateThread extends Thread { |
795 | |
796 | private final File file = new File("translation.properties"); |
797 | private final Map<Object, Object> translation; |
798 | private volatile boolean stopNow; |
799 | |
800 | TranslateThread(Map<Object, Object> translation) { |
801 | this.translation = translation; |
802 | } |
803 | |
804 | public String getFileName() { |
805 | return file.getAbsolutePath(); |
806 | } |
807 | |
808 | public void stopNow() { |
809 | this.stopNow = true; |
810 | try { |
811 | join(); |
812 | } catch (InterruptedException e) { |
813 | // ignore |
814 | } |
815 | } |
816 | |
817 | @Override |
818 | public void run() { |
819 | while (!stopNow) { |
820 | try { |
821 | SortedProperties sp = new SortedProperties(); |
822 | if (file.exists()) { |
823 | InputStream in = FileUtils.newInputStream(file.getName()); |
824 | sp.load(in); |
825 | translation.putAll(sp); |
826 | } else { |
827 | OutputStream out = FileUtils.newOutputStream(file.getName(), false); |
828 | sp.putAll(translation); |
829 | sp.store(out, "Translation"); |
830 | } |
831 | Thread.sleep(1000); |
832 | } catch (Exception e) { |
833 | traceError(e); |
834 | } |
835 | } |
836 | } |
837 | |
838 | } |
839 | |
840 | /** |
841 | * Start the translation thread that reads the file once a second. |
842 | * |
843 | * @param translation the translation map |
844 | * @return the name of the file to translate |
845 | */ |
846 | String startTranslate(Map<Object, Object> translation) { |
847 | if (translateThread != null) { |
848 | translateThread.stopNow(); |
849 | } |
850 | translateThread = new TranslateThread(translation); |
851 | translateThread.setDaemon(true); |
852 | translateThread.start(); |
853 | return translateThread.getFileName(); |
854 | } |
855 | |
856 | @Override |
857 | public boolean isDaemon() { |
858 | return isDaemon; |
859 | } |
860 | |
861 | void setAllowChunked(boolean allowChunked) { |
862 | this.allowChunked = allowChunked; |
863 | } |
864 | |
865 | boolean getAllowChunked() { |
866 | return allowChunked; |
867 | } |
868 | |
869 | } |