001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.server.web;
007:
008: import java.io.ByteArrayInputStream;
009: import java.io.IOException;
010: import java.io.OutputStream;
011: import java.net.ServerSocket;
012: import java.net.Socket;
013: import java.sql.Connection;
014: import java.sql.SQLException;
015: import java.sql.Statement;
016: import java.text.SimpleDateFormat;
017: import java.util.ArrayList;
018: import java.util.Collections;
019: import java.util.Date;
020: import java.util.HashMap;
021: import java.util.HashSet;
022: import java.util.Locale;
023: import java.util.Properties;
024: import java.util.Set;
025: import java.util.TimeZone;
026:
027: import org.h2.api.DatabaseEventListener;
028: import org.h2.constant.SysProperties;
029: import org.h2.engine.Constants;
030: import org.h2.message.TraceSystem;
031: import org.h2.server.Service;
032: import org.h2.server.ShutdownHandler;
033: import org.h2.util.ByteUtils;
034: import org.h2.util.FileUtils;
035: import org.h2.util.JdbcUtils;
036: import org.h2.util.MathUtils;
037: import org.h2.util.NetUtils;
038: import org.h2.util.RandomUtils;
039: import org.h2.util.Resources;
040: import org.h2.util.SortedProperties;
041:
042: /**
043: * The web server is a simple standalone HTTP server that implements the H2
044: * Console application. It is not optimized for performance.
045: */
046: public class WebServer implements Service {
047:
048: private static final String DEFAULT_LANGUAGE = "en";
049:
050: private static final String[][] LANGUAGES = {
051: { "de", "Deutsch" },
052: { "en", "English" },
053: { "es", "Espa\u00f1ol" },
054: { "fr", "Fran\u00e7ais" },
055: { "hu", "Magyar" },
056: { "in", "Indonesia" },
057: { "it", "Italiano" },
058: { "ja", "\u65e5\u672c\u8a9e" },
059: { "nl", "Nederlands" },
060: { "pl", "Polski" },
061: { "pt_BR", "Portugu\u00eas (Brasil)" },
062: { "pt_PT", "Portugu\u00eas (Europeu)" },
063: { "ru", "\u0440\u0443\u0441\u0441\u043a\u0438\u0439" },
064: { "tr", "T\u00fcrk\u00e7e" },
065: { "uk",
066: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430" },
067: { "zh_CN", "\u4E2D\u6587" }, };
068:
069: private static final String[] GENERIC = new String[] {
070: "Generic JNDI Data Source|javax.naming.InitialContext|java:comp/env/jdbc/Test|sa",
071: "Generic Firebird Server|org.firebirdsql.jdbc.FBDriver|jdbc:firebirdsql:localhost:c:/temp/firebird/test|sysdba",
072: "Generic OneDollarDB|in.co.daffodil.db.jdbc.DaffodilDBDriver|jdbc:daffodilDB_embedded:school;path=C:/temp;create=true|sa",
073: "Generic DB2|COM.ibm.db2.jdbc.net.DB2Driver|jdbc:db2://<host>/<db>|",
074: "Generic Oracle|oracle.jdbc.driver.OracleDriver|jdbc:oracle:thin:@<host>:1521:<instance>|scott",
075: "Generic MS SQL Server 2000|com.microsoft.jdbc.sqlserver.SQLServerDriver|jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=sqlexpress|sa",
076: "Generic MS SQL Server 2005|com.microsoft.sqlserver.jdbc.SQLServerDriver|jdbc:sqlserver://localhost;DatabaseName=test|sa",
077: "Generic PostgreSQL|org.postgresql.Driver|jdbc:postgresql:<db>|",
078: "Generic MySQL|com.mysql.jdbc.Driver|jdbc:mysql://<host>:<port>/<db>|",
079: "Generic Derby (Server)|org.apache.derby.jdbc.ClientDriver|jdbc:derby://localhost:1527/test;create=true|sa",
080: "Generic Derby (Embedded)|org.apache.derby.jdbc.EmbeddedDriver|jdbc:derby:test;create=true|sa",
081: "Generic HSQLDB|org.hsqldb.jdbcDriver|jdbc:hsqldb:test;hsqldb.default_table_type=cached|sa",
082: // this will be listed on top for new installations
083: "Generic H2|org.h2.Driver|jdbc:h2:~/test|sa", };
084:
085: /*
086: String[] list = Locale.getISOLanguages();
087: for(int i=0; i<list.length; i++) System.out.print(list[i] + " ");
088: String lang = new java.util.Locale("hu").
089: getDisplayLanguage(new java.util.Locale("hu"));
090: java.util.Locale.CHINESE.getDisplayLanguage(
091: java.util.Locale.CHINESE);
092: for(int i=0; i<lang.length(); i++)
093: System.out.println(Integer.toHexString(lang.charAt(i))+" ");
094: */
095:
096: // private URLClassLoader urlClassLoader;
097: private String driverList;
098: private static int ticker;
099: private int port;
100: private boolean allowOthers;
101: private Set running = Collections.synchronizedSet(new HashSet());
102: private boolean ssl;
103: private HashMap connInfoMap = new HashMap();
104:
105: private static final long SESSION_TIMEOUT = 30 * 60 * 1000; // timeout is 30 min
106: private long lastTimeoutCheck;
107: private HashMap sessions = new HashMap();
108: private HashSet languages = new HashSet();
109: private String startDateTime;
110: private ServerSocket serverSocket;
111: private String url;
112: private ShutdownHandler shutdownHandler;
113: private Thread listenerThread;
114: private boolean ifExists;
115: private boolean allowScript;
116:
117: byte[] getFile(String file) throws IOException {
118: trace("getFile <" + file + ">");
119: byte[] data = Resources.get("/org/h2/server/web/res/" + file);
120: if (data == null) {
121: trace(" null");
122: } else {
123: trace(" size=" + data.length);
124: }
125: return data;
126: }
127:
128: String getTextFile(String file) throws IOException {
129: byte[] bytes = getFile(file);
130: return new String(bytes);
131: }
132:
133: synchronized void remove(WebThread t) {
134: running.remove(t);
135: }
136:
137: private String generateSessionId() {
138: byte[] buff = RandomUtils.getSecureBytes(16);
139: return ByteUtils.convertBytesToString(buff);
140: }
141:
142: WebSession getSession(String sessionId) {
143: long now = System.currentTimeMillis();
144: if (lastTimeoutCheck + SESSION_TIMEOUT < now) {
145: Object[] list = sessions.keySet().toArray();
146: for (int i = 0; i < list.length; i++) {
147: String id = (String) list[i];
148: WebSession session = (WebSession) sessions.get(id);
149: Long last = (Long) session.get("lastAccess");
150: if (last != null
151: && last.longValue() + SESSION_TIMEOUT < now) {
152: trace("timeout for " + id);
153: sessions.remove(id);
154: }
155: }
156: lastTimeoutCheck = now;
157: }
158: WebSession session = (WebSession) sessions.get(sessionId);
159: if (session != null) {
160: session.lastAccess = System.currentTimeMillis();
161: }
162: return session;
163: }
164:
165: WebSession createNewSession(String hostAddr) {
166: String newId;
167: do {
168: newId = generateSessionId();
169: } while (sessions.get(newId) != null);
170: WebSession session = new WebSession(this );
171: session.put("sessionId", newId);
172: session.put("ip", hostAddr);
173: session.put("language", DEFAULT_LANGUAGE);
174: sessions.put(newId, session);
175: // always read the english translation,
176: // to that untranslated text appears at least in english
177: readTranslations(session, DEFAULT_LANGUAGE);
178: return getSession(newId);
179: }
180:
181: String getStartDateTime() {
182: return startDateTime;
183: }
184:
185: public void init(String[] args) throws Exception {
186: // TODO web: support using a different properties file
187: Properties prop = loadProperties();
188: driverList = prop.getProperty("drivers");
189: port = FileUtils.getIntProperty(prop, "webPort",
190: Constants.DEFAULT_HTTP_PORT);
191: ssl = FileUtils.getBooleanProperty(prop, "webSSL",
192: Constants.DEFAULT_HTTP_SSL);
193: allowOthers = FileUtils.getBooleanProperty(prop,
194: "webAllowOthers", Constants.DEFAULT_HTTP_ALLOW_OTHERS);
195: for (int i = 0; args != null && i < args.length; i++) {
196: String a = args[i];
197: if ("-webPort".equals(a)) {
198: port = MathUtils.decodeInt(args[++i]);
199: } else if ("-webSSL".equals(a)) {
200: ssl = Boolean.valueOf(args[++i]).booleanValue();
201: } else if ("-webAllowOthers".equals(a)) {
202: allowOthers = Boolean.valueOf(args[++i]).booleanValue();
203: } else if ("-webScript".equals(a)) {
204: allowScript = Boolean.valueOf(args[++i]).booleanValue();
205: } else if ("-baseDir".equals(a)) {
206: String baseDir = args[++i];
207: SysProperties.setBaseDir(baseDir);
208: } else if ("-ifExists".equals(a)) {
209: ifExists = Boolean.valueOf(args[++i]).booleanValue();
210: }
211: }
212: // if(driverList != null) {
213: // try {
214: // String[] drivers =
215: // StringUtils.arraySplit(driverList, ',', false);
216: // URL[] urls = new URL[drivers.length];
217: // for(int i=0; i<drivers.length; i++) {
218: // urls[i] = new URL(drivers[i]);
219: // }
220: // urlClassLoader = URLClassLoader.newInstance(urls);
221: // } catch (MalformedURLException e) {
222: // TraceSystem.traceThrowable(e);
223: // }
224: // }
225: SimpleDateFormat format = new SimpleDateFormat(
226: "EEE, d MMM yyyy HH:mm:ss z", new Locale("en", ""));
227: synchronized (format) {
228: format.setTimeZone(TimeZone.getTimeZone("GMT"));
229: startDateTime = format.format(new Date());
230: }
231: trace(startDateTime);
232: for (int i = 0; i < LANGUAGES.length; i++) {
233: languages.add(LANGUAGES[i][0]);
234: }
235: url = (ssl ? "https" : "http") + "://"
236: + NetUtils.getLocalAddress() + ":" + port;
237: }
238:
239: public String getURL() {
240: return url;
241: }
242:
243: public void start() throws SQLException {
244: serverSocket = NetUtils.createServerSocket(port, ssl);
245: }
246:
247: public void listen() {
248: this .listenerThread = Thread.currentThread();
249: try {
250: while (serverSocket != null) {
251: Socket s = serverSocket.accept();
252: WebThread c = new WebThread(s, this );
253: running.add(c);
254: c.start();
255: }
256: } catch (Exception e) {
257: trace(e.toString());
258: }
259: }
260:
261: public boolean isRunning() {
262: if (serverSocket == null) {
263: return false;
264: }
265: try {
266: Socket s = NetUtils.createLoopbackSocket(port, ssl);
267: s.close();
268: return true;
269: } catch (Exception e) {
270: return false;
271: }
272: }
273:
274: public void stop() {
275: try {
276: serverSocket.close();
277: } catch (IOException e) {
278: // TODO log exception
279: }
280: serverSocket = null;
281: if (listenerThread != null) {
282: try {
283: listenerThread.join(1000);
284: } catch (InterruptedException e) {
285: TraceSystem.traceThrowable(e);
286: }
287: }
288: // TODO server: using a boolean 'now' argument? a timeout?
289: ArrayList list = new ArrayList(sessions.values());
290: for (int i = 0; i < list.size(); i++) {
291: WebSession session = (WebSession) list.get(i);
292: Statement stat = session.executingStatement;
293: if (stat != null) {
294: try {
295: stat.cancel();
296: } catch (Exception e) {
297: // ignore
298: }
299: }
300: }
301: list = new ArrayList(running);
302: for (int i = 0; i < list.size(); i++) {
303: WebThread c = (WebThread) list.get(i);
304: try {
305: c.stopNow();
306: c.join(100);
307: } catch (Exception e) {
308: // TODO log exception
309: e.printStackTrace();
310: }
311: }
312: }
313:
314: void trace(String s) {
315: // System.out.println(s);
316: }
317:
318: public void traceError(Exception e) {
319: e.printStackTrace();
320: }
321:
322: public boolean supportsLanguage(String language) {
323: return languages.contains(language);
324: }
325:
326: public void readTranslations(WebSession session, String language) {
327: Properties text = new Properties();
328: try {
329: trace("translation: " + language);
330: byte[] trans = getFile("_text_" + language + ".properties");
331: trace(" " + new String(trans));
332: text.load(new ByteArrayInputStream(trans));
333: } catch (IOException e) {
334: TraceSystem.traceThrowable(e);
335: }
336: session.put("text", new HashMap(text));
337: }
338:
339: public String[][] getLanguageArray() {
340: return LANGUAGES;
341: }
342:
343: public ArrayList getSessions() {
344: ArrayList list = new ArrayList(sessions.values());
345: for (int i = 0; i < list.size(); i++) {
346: WebSession s = (WebSession) list.get(i);
347: list.set(i, s.getInfo());
348: }
349: return list;
350: }
351:
352: public String getType() {
353: return "Web";
354: }
355:
356: public String getName() {
357: return "H2 Console Server";
358: }
359:
360: void setAllowOthers(boolean b) {
361: allowOthers = b;
362: }
363:
364: public boolean getAllowOthers() {
365: return allowOthers;
366: }
367:
368: void setSSL(boolean b) {
369: ssl = b;
370: }
371:
372: void setPort(int port) {
373: this .port = port;
374: }
375:
376: boolean getSSL() {
377: return ssl;
378: }
379:
380: int getPort() {
381: return port;
382: }
383:
384: ConnectionInfo getSetting(String name) {
385: return (ConnectionInfo) connInfoMap.get(name);
386: }
387:
388: void updateSetting(ConnectionInfo info) {
389: connInfoMap.put(info.name, info);
390: info.lastAccess = ticker++;
391: }
392:
393: void removeSetting(String name) {
394: connInfoMap.remove(name);
395: }
396:
397: private String getPropertiesFileName() {
398: // store the properties in the user directory
399: return FileUtils
400: .getFileInUserHome(Constants.SERVER_PROPERTIES_FILE);
401: }
402:
403: Properties loadProperties() {
404: String fileName = getPropertiesFileName();
405: try {
406: return FileUtils.loadProperties(fileName);
407: } catch (IOException e) {
408: // TODO log exception
409: return new Properties();
410: }
411: }
412:
413: String[] getSettingNames() {
414: ArrayList list = getSettings();
415: String[] names = new String[list.size()];
416: for (int i = 0; i < list.size(); i++) {
417: names[i] = ((ConnectionInfo) list.get(i)).name;
418: }
419: return names;
420: }
421:
422: synchronized ArrayList getSettings() {
423: ArrayList settings = new ArrayList();
424: if (connInfoMap.size() == 0) {
425: Properties prop = loadProperties();
426: if (prop.size() == 0) {
427: for (int i = 0; i < GENERIC.length; i++) {
428: ConnectionInfo info = new ConnectionInfo(GENERIC[i]);
429: settings.add(info);
430: updateSetting(info);
431: }
432: } else {
433: for (int i = 0;; i++) {
434: String data = prop.getProperty(String.valueOf(i));
435: if (data == null) {
436: break;
437: }
438: ConnectionInfo info = new ConnectionInfo(data);
439: settings.add(info);
440: updateSetting(info);
441: }
442: }
443: } else {
444: settings.addAll(connInfoMap.values());
445: }
446: sortConnectionInfo(settings);
447: return settings;
448: }
449:
450: void sortConnectionInfo(ArrayList list) {
451: for (int i = 1, j; i < list.size(); i++) {
452: ConnectionInfo t = (ConnectionInfo) list.get(i);
453: for (j = i - 1; j >= 0
454: && (((ConnectionInfo) list.get(j)).lastAccess < t.lastAccess); j--) {
455: list.set(j + 1, list.get(j));
456: }
457: list.set(j + 1, t);
458: }
459: }
460:
461: synchronized void saveSettings() {
462: try {
463: Properties prop = new SortedProperties();
464: if (driverList != null) {
465: prop.setProperty("drivers", driverList);
466: }
467: prop.setProperty("webPort", String.valueOf(port));
468: prop.setProperty("webAllowOthers", String
469: .valueOf(allowOthers));
470: prop.setProperty("webSSL", String.valueOf(ssl));
471: ArrayList settings = getSettings();
472: int len = settings.size();
473: for (int i = 0; i < len; i++) {
474: ConnectionInfo info = (ConnectionInfo) settings.get(i);
475: if (info != null) {
476: prop.setProperty(String.valueOf(len - i - 1), info
477: .getString());
478: }
479: }
480: OutputStream out = FileUtils.openFileOutputStream(
481: getPropertiesFileName(), false);
482: prop.store(out, Constants.SERVER_PROPERTIES_TITLE);
483: out.close();
484: } catch (Exception e) {
485: TraceSystem.traceThrowable(e);
486: }
487: }
488:
489: Connection getConnection(String driver, String url, String user,
490: String password, DatabaseEventListener listener)
491: throws Exception {
492: driver = driver.trim();
493: url = url.trim();
494: org.h2.Driver.load();
495: Properties p = new Properties();
496: p.setProperty("user", user.trim());
497: p.setProperty("password", password.trim());
498: if (url.startsWith("jdbc:h2:")) {
499: if (ifExists) {
500: url += ";IFEXISTS=TRUE";
501: }
502: p.put("DATABASE_EVENT_LISTENER_OBJECT", listener);
503: // PostgreSQL would throw a NullPointerException
504: // if it is loaded before the H2 driver
505: // because it can't deal with non-String objects in the connection Properties
506: return org.h2.Driver.load().connect(url, p);
507: }
508: // try {
509: // Driver dr = (Driver) urlClassLoader.
510: // loadClass(driver).newInstance();
511: // return dr.connect(url, p);
512: // } catch(ClassNotFoundException e2) {
513: // throw e2;
514: // }
515: return JdbcUtils.getConnection(driver, url, p);
516: }
517:
518: void shutdown() {
519: if (shutdownHandler != null) {
520: shutdownHandler.shutdown();
521: }
522: }
523:
524: public void setShutdownHandler(ShutdownHandler shutdownHandler) {
525: this .shutdownHandler = shutdownHandler;
526: }
527:
528: public boolean getAllowScript() {
529: return allowScript;
530: }
531:
532: }
|