File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\server\web\WebServer.java.html

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