https://tree.taiga.io/project/jr-utily-grog-v3/us/195 Mettre en place une connexion h2 via servlet
Showing
7 changed files
with
3959 additions
and
0 deletions
1 | +package org.legrog.util; | ||
2 | +// Code copied from org.h2.server.web.ConnectionInfo | ||
3 | +/* | ||
4 | + * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, | ||
5 | + * and the EPL 1.0 (http://h2database.com/html/license.html). | ||
6 | + * Initial Developer: H2 Group | ||
7 | + */ | ||
8 | +//package org.h2.server.web; | ||
9 | + | ||
10 | + import org.h2.util.MathUtils; | ||
11 | + import org.h2.util.StringUtils; | ||
12 | + | ||
13 | +/** | ||
14 | + * The connection info object is a wrapper for database connection information | ||
15 | + * such as the database URL, user name and password. | ||
16 | + * This class is used by the H2 Console. | ||
17 | + */ | ||
18 | +public class ConnectionInfo implements Comparable<ConnectionInfo> { | ||
19 | + /** | ||
20 | + * The driver class name. | ||
21 | + */ | ||
22 | + public String driver; | ||
23 | + | ||
24 | + /** | ||
25 | + * The database URL. | ||
26 | + */ | ||
27 | + public String url; | ||
28 | + | ||
29 | + /** | ||
30 | + * The user name. | ||
31 | + */ | ||
32 | + public String user; | ||
33 | + | ||
34 | + /** | ||
35 | + * The connection display name. | ||
36 | + */ | ||
37 | + String name; | ||
38 | + | ||
39 | + /** | ||
40 | + * The last time this connection was used. | ||
41 | + */ | ||
42 | + int lastAccess; | ||
43 | + | ||
44 | + ConnectionInfo() { | ||
45 | + // nothing to do | ||
46 | + } | ||
47 | + | ||
48 | + public ConnectionInfo(String data) { | ||
49 | + String[] array = StringUtils.arraySplit(data, '|', false); | ||
50 | + name = get(array, 0); | ||
51 | + driver = get(array, 1); | ||
52 | + url = get(array, 2); | ||
53 | + user = get(array, 3); | ||
54 | + } | ||
55 | + | ||
56 | + private static String get(String[] array, int i) { | ||
57 | + return array != null && array.length > i ? array[i] : ""; | ||
58 | + } | ||
59 | + | ||
60 | + String getString() { | ||
61 | + return StringUtils.arrayCombine(new String[] { name, driver, url, user }, '|'); | ||
62 | + } | ||
63 | + | ||
64 | + @Override | ||
65 | + public int compareTo(ConnectionInfo o) { | ||
66 | + return -MathUtils.compareInt(lastAccess, o.lastAccess); | ||
67 | + } | ||
68 | + | ||
69 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +package org.legrog.util; | ||
2 | + | ||
3 | +import javax.servlet.annotation.WebServlet; | ||
4 | + | ||
5 | +import java.io.IOException; | ||
6 | +import java.net.InetAddress; | ||
7 | +import java.net.UnknownHostException; | ||
8 | +import java.util.ArrayList; | ||
9 | +import java.util.Enumeration; | ||
10 | +import java.util.Properties; | ||
11 | + | ||
12 | +import javax.servlet.ServletConfig; | ||
13 | +import javax.servlet.ServletOutputStream; | ||
14 | +import javax.servlet.http.HttpServlet; | ||
15 | +import javax.servlet.http.HttpServletRequest; | ||
16 | +import javax.servlet.http.HttpServletResponse; | ||
17 | + | ||
18 | +import org.h2.engine.Constants; | ||
19 | +import org.h2.server.web.PageParser; | ||
20 | +import org.h2.util.New; | ||
21 | + | ||
22 | + | ||
23 | +@WebServlet(urlPatterns="/console/*", name="H2Console", loadOnStartup=1) | ||
24 | +public class H2ConsoleServlet extends HttpServlet { | ||
25 | +// Code copied from org.h2.server.web.WebServlet | ||
26 | + private static final long serialVersionUID = 1L; | ||
27 | + private transient WebServer server; | ||
28 | + | ||
29 | + @Override | ||
30 | + public void init() { | ||
31 | + ServletConfig config = getServletConfig(); | ||
32 | + Enumeration<?> en = config.getInitParameterNames(); | ||
33 | + ArrayList<String> list = New.arrayList(); | ||
34 | + while (en.hasMoreElements()) { | ||
35 | + String name = en.nextElement().toString(); | ||
36 | + String value = config.getInitParameter(name); | ||
37 | + if (!name.startsWith("-")) { | ||
38 | + name = "-" + name; | ||
39 | + } | ||
40 | + list.add(name); | ||
41 | + if (value.length() > 0) { | ||
42 | + list.add(value); | ||
43 | + } | ||
44 | + } | ||
45 | + String[] args = new String[list.size()]; | ||
46 | + list.toArray(args); | ||
47 | + server = new WebServer(); | ||
48 | + server.setAllowChunked(false); | ||
49 | + server.init(args); | ||
50 | + } | ||
51 | + | ||
52 | + @Override | ||
53 | + public void destroy() { | ||
54 | + server.stop(); | ||
55 | + } | ||
56 | + | ||
57 | + private boolean allow(HttpServletRequest req) { | ||
58 | + if (server.getAllowOthers()) { | ||
59 | + return true; | ||
60 | + } | ||
61 | + String addr = req.getRemoteAddr(); | ||
62 | + try { | ||
63 | + InetAddress address = InetAddress.getByName(addr); | ||
64 | + return address.isLoopbackAddress(); | ||
65 | + } catch (UnknownHostException e) { | ||
66 | + return false; | ||
67 | + } catch (NoClassDefFoundError e) { | ||
68 | + // Google App Engine does not allow java.net.InetAddress | ||
69 | + return false; | ||
70 | + } | ||
71 | + } | ||
72 | + | ||
73 | + private String getAllowedFile(HttpServletRequest req, String requestedFile) { | ||
74 | + if (!allow(req)) { | ||
75 | + return "notAllowed.jsp"; | ||
76 | + } | ||
77 | + if (requestedFile.length() == 0) { | ||
78 | + return "index.do"; | ||
79 | + } | ||
80 | + return requestedFile; | ||
81 | + } | ||
82 | + | ||
83 | + @Override | ||
84 | + public void doGet(HttpServletRequest req, HttpServletResponse resp) | ||
85 | + throws IOException { | ||
86 | + req.setCharacterEncoding("utf-8"); | ||
87 | + String file = req.getPathInfo(); | ||
88 | + if (file == null) { | ||
89 | + resp.sendRedirect(req.getRequestURI() + "/"); | ||
90 | + return; | ||
91 | + } else if (file.startsWith("/")) { | ||
92 | + file = file.substring(1); | ||
93 | + } | ||
94 | + file = getAllowedFile(req, file); | ||
95 | + | ||
96 | + // extract the request attributes | ||
97 | + Properties attributes = new Properties(); | ||
98 | + Enumeration<?> en = req.getAttributeNames(); | ||
99 | + while (en.hasMoreElements()) { | ||
100 | + String name = en.nextElement().toString(); | ||
101 | + String value = req.getAttribute(name).toString(); | ||
102 | + attributes.put(name, value); | ||
103 | + } | ||
104 | + en = req.getParameterNames(); | ||
105 | + while (en.hasMoreElements()) { | ||
106 | + String name = en.nextElement().toString(); | ||
107 | + String value = req.getParameter(name); | ||
108 | + attributes.put(name, value); | ||
109 | + } | ||
110 | + | ||
111 | + WebSession session = null; | ||
112 | + String sessionId = attributes.getProperty("jsessionid"); | ||
113 | + if (sessionId != null) { | ||
114 | + session = server.getSession(sessionId); | ||
115 | + } | ||
116 | + WebApp app = new WebApp(server); | ||
117 | + app.setSession(session, attributes); | ||
118 | + String ifModifiedSince = req.getHeader("if-modified-since"); | ||
119 | + | ||
120 | + String hostAddr = req.getRemoteAddr(); | ||
121 | + file = app.processRequest(file, hostAddr); | ||
122 | + session = app.getSession(); | ||
123 | + | ||
124 | + String mimeType = app.getMimeType(); | ||
125 | + boolean cache = app.getCache(); | ||
126 | + | ||
127 | + if (cache && server.getStartDateTime().equals(ifModifiedSince)) { | ||
128 | + resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); | ||
129 | + return; | ||
130 | + } | ||
131 | + byte[] bytes = server.getFile(file); | ||
132 | + if (bytes == null) { | ||
133 | + resp.sendError(HttpServletResponse.SC_NOT_FOUND); | ||
134 | + bytes = ("File not found: " + file).getBytes(Constants.UTF8); | ||
135 | + } else { | ||
136 | + if (session != null && file.endsWith(".jsp")) { | ||
137 | + String page = new String(bytes, Constants.UTF8); | ||
138 | + page = PageParser.parse(page, session.map); | ||
139 | + bytes = page.getBytes(Constants.UTF8); | ||
140 | + } | ||
141 | + resp.setContentType(mimeType); | ||
142 | + if (!cache) { | ||
143 | + resp.setHeader("Cache-Control", "no-cache"); | ||
144 | + } else { | ||
145 | + resp.setHeader("Cache-Control", "max-age=10"); | ||
146 | + resp.setHeader("Last-Modified", server.getStartDateTime()); | ||
147 | + } | ||
148 | + } | ||
149 | + if (bytes != null) { | ||
150 | + ServletOutputStream out = resp.getOutputStream(); | ||
151 | + out.write(bytes); | ||
152 | + } | ||
153 | + } | ||
154 | + | ||
155 | + @Override | ||
156 | + public void doPost(HttpServletRequest req, HttpServletResponse resp) | ||
157 | + throws IOException { | ||
158 | + doGet(req, resp); | ||
159 | + } | ||
160 | +} |
1 | +package org.legrog.util; | ||
2 | +// Code copied from org.h2.server.web.PageParser | ||
3 | +/* | ||
4 | + * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, | ||
5 | + * and the EPL 1.0 (http://h2database.com/html/license.html). | ||
6 | + * Initial Developer: H2 Group | ||
7 | + */ | ||
8 | +//package org.h2.server.web; | ||
9 | + | ||
10 | + import java.text.ParseException; | ||
11 | + import java.util.HashMap; | ||
12 | + import java.util.List; | ||
13 | + import java.util.Map; | ||
14 | + import org.h2.util.New; | ||
15 | + | ||
16 | +/** | ||
17 | + * A page parser can parse an HTML page and replace the tags there. | ||
18 | + * This class is used by the H2 Console. | ||
19 | + */ | ||
20 | +public class PageParser { | ||
21 | + private static final int TAB_WIDTH = 4; | ||
22 | + | ||
23 | + private final String page; | ||
24 | + private int pos; | ||
25 | + private final Map<String, Object> settings; | ||
26 | + private final int len; | ||
27 | + private StringBuilder result; | ||
28 | + | ||
29 | + private PageParser(String page, Map<String, Object> settings, int pos) { | ||
30 | + this.page = page; | ||
31 | + this.pos = pos; | ||
32 | + this.len = page.length(); | ||
33 | + this.settings = settings; | ||
34 | + result = new StringBuilder(len); | ||
35 | + } | ||
36 | + | ||
37 | + /** | ||
38 | + * Replace the tags in the HTML page with the given settings. | ||
39 | + * | ||
40 | + * @param page the HTML page | ||
41 | + * @param settings the settings | ||
42 | + * @return the converted page | ||
43 | + */ | ||
44 | + public static String parse(String page, Map<String, Object> settings) { | ||
45 | + PageParser block = new PageParser(page, settings, 0); | ||
46 | + return block.replaceTags(); | ||
47 | + } | ||
48 | + | ||
49 | + private void setError(int i) { | ||
50 | + String s = page.substring(0, i) + "####BUG####" + page.substring(i); | ||
51 | + s = PageParser.escapeHtml(s); | ||
52 | + result = new StringBuilder(); | ||
53 | + result.append(s); | ||
54 | + } | ||
55 | + | ||
56 | + private String parseBlockUntil(String end) throws ParseException { | ||
57 | + PageParser block = new PageParser(page, settings, pos); | ||
58 | + block.parseAll(); | ||
59 | + if (!block.readIf(end)) { | ||
60 | + throw new ParseException(page, block.pos); | ||
61 | + } | ||
62 | + pos = block.pos; | ||
63 | + return block.result.toString(); | ||
64 | + } | ||
65 | + | ||
66 | + private String replaceTags() { | ||
67 | + try { | ||
68 | + parseAll(); | ||
69 | + if (pos != len) { | ||
70 | + setError(pos); | ||
71 | + } | ||
72 | + } catch (ParseException e) { | ||
73 | + setError(pos); | ||
74 | + } | ||
75 | + return result.toString(); | ||
76 | + } | ||
77 | + | ||
78 | + @SuppressWarnings("unchecked") | ||
79 | + private void parseAll() throws ParseException { | ||
80 | + StringBuilder buff = result; | ||
81 | + String p = page; | ||
82 | + int i = pos; | ||
83 | + for (; i < len; i++) { | ||
84 | + char c = p.charAt(i); | ||
85 | + switch (c) { | ||
86 | + case '<': { | ||
87 | + if (p.charAt(i + 3) == ':' && p.charAt(i + 1) == '/') { | ||
88 | + // end tag | ||
89 | + pos = i; | ||
90 | + return; | ||
91 | + } else if (p.charAt(i + 2) == ':') { | ||
92 | + pos = i; | ||
93 | + if (readIf("<c:forEach")) { | ||
94 | + String var = readParam("var"); | ||
95 | + String items = readParam("items"); | ||
96 | + read(">"); | ||
97 | + int start = pos; | ||
98 | + List<Object> list = (List<Object>) get(items); | ||
99 | + if (list == null) { | ||
100 | + result.append("?items?"); | ||
101 | + list = New.arrayList(); | ||
102 | + } | ||
103 | + if (list.size() == 0) { | ||
104 | + parseBlockUntil("</c:forEach>"); | ||
105 | + } | ||
106 | + for (Object o : list) { | ||
107 | + settings.put(var, o); | ||
108 | + pos = start; | ||
109 | + String block = parseBlockUntil("</c:forEach>"); | ||
110 | + result.append(block); | ||
111 | + } | ||
112 | + } else if (readIf("<c:if")) { | ||
113 | + String test = readParam("test"); | ||
114 | + int eq = test.indexOf("=='"); | ||
115 | + if (eq < 0) { | ||
116 | + setError(i); | ||
117 | + return; | ||
118 | + } | ||
119 | + String val = test.substring(eq + 3, test.length() - 1); | ||
120 | + test = test.substring(0, eq); | ||
121 | + String value = (String) get(test); | ||
122 | + read(">"); | ||
123 | + String block = parseBlockUntil("</c:if>"); | ||
124 | + pos--; | ||
125 | + if (value.equals(val)) { | ||
126 | + result.append(block); | ||
127 | + } | ||
128 | + } else { | ||
129 | + setError(i); | ||
130 | + return; | ||
131 | + } | ||
132 | + i = pos; | ||
133 | + } else { | ||
134 | + buff.append(c); | ||
135 | + } | ||
136 | + break; | ||
137 | + } | ||
138 | + case '$': | ||
139 | + if (p.length() > i + 1 && p.charAt(i + 1) == '{') { | ||
140 | + i += 2; | ||
141 | + int j = p.indexOf('}', i); | ||
142 | + if (j < 0) { | ||
143 | + setError(i); | ||
144 | + return; | ||
145 | + } | ||
146 | + String item = p.substring(i, j).trim(); | ||
147 | + i = j; | ||
148 | + String s = (String) get(item); | ||
149 | + replaceTags(s); | ||
150 | + } else { | ||
151 | + buff.append(c); | ||
152 | + } | ||
153 | + break; | ||
154 | + default: | ||
155 | + buff.append(c); | ||
156 | + break; | ||
157 | + } | ||
158 | + } | ||
159 | + pos = i; | ||
160 | + } | ||
161 | + | ||
162 | + @SuppressWarnings("unchecked") | ||
163 | + private Object get(String item) { | ||
164 | + int dot = item.indexOf('.'); | ||
165 | + if (dot >= 0) { | ||
166 | + String sub = item.substring(dot + 1); | ||
167 | + item = item.substring(0, dot); | ||
168 | + HashMap<String, Object> map = (HashMap<String, Object>) settings.get(item); | ||
169 | + if (map == null) { | ||
170 | + return "?" + item + "?"; | ||
171 | + } | ||
172 | + return map.get(sub); | ||
173 | + } | ||
174 | + return settings.get(item); | ||
175 | + } | ||
176 | + | ||
177 | + private void replaceTags(String s) { | ||
178 | + if (s != null) { | ||
179 | + result.append(PageParser.parse(s, settings)); | ||
180 | + } | ||
181 | + } | ||
182 | + | ||
183 | + private String readParam(String name) throws ParseException { | ||
184 | + read(name); | ||
185 | + read("="); | ||
186 | + read("\""); | ||
187 | + int start = pos; | ||
188 | + while (page.charAt(pos) != '"') { | ||
189 | + pos++; | ||
190 | + } | ||
191 | + int end = pos; | ||
192 | + read("\""); | ||
193 | + String s = page.substring(start, end); | ||
194 | + return PageParser.parse(s, settings); | ||
195 | + } | ||
196 | + | ||
197 | + private void skipSpaces() { | ||
198 | + while (page.charAt(pos) == ' ') { | ||
199 | + pos++; | ||
200 | + } | ||
201 | + } | ||
202 | + | ||
203 | + private void read(String s) throws ParseException { | ||
204 | + if (!readIf(s)) { | ||
205 | + throw new ParseException(s, pos); | ||
206 | + } | ||
207 | + } | ||
208 | + | ||
209 | + private boolean readIf(String s) { | ||
210 | + skipSpaces(); | ||
211 | + if (page.regionMatches(pos, s, 0, s.length())) { | ||
212 | + pos += s.length(); | ||
213 | + skipSpaces(); | ||
214 | + return true; | ||
215 | + } | ||
216 | + return false; | ||
217 | + } | ||
218 | + | ||
219 | + /** | ||
220 | + * Convert data to HTML, but don't convert newlines and multiple spaces. | ||
221 | + * | ||
222 | + * @param s the data | ||
223 | + * @return the escaped html text | ||
224 | + */ | ||
225 | + static String escapeHtmlData(String s) { | ||
226 | + return escapeHtml(s, false); | ||
227 | + } | ||
228 | + | ||
229 | + /** | ||
230 | + * Convert data to HTML, including newlines and multiple spaces. | ||
231 | + * | ||
232 | + * @param s the data | ||
233 | + * @return the escaped html text | ||
234 | + */ | ||
235 | + public static String escapeHtml(String s) { | ||
236 | + return escapeHtml(s, true); | ||
237 | + } | ||
238 | + | ||
239 | + private static String escapeHtml(String s, boolean convertBreakAndSpace) { | ||
240 | + if (s == null) { | ||
241 | + return null; | ||
242 | + } | ||
243 | + if (convertBreakAndSpace) { | ||
244 | + if (s.length() == 0) { | ||
245 | + return " "; | ||
246 | + } | ||
247 | + } | ||
248 | + StringBuilder buff = new StringBuilder(s.length()); | ||
249 | + boolean convertSpace = true; | ||
250 | + for (int i = 0; i < s.length(); i++) { | ||
251 | + char c = s.charAt(i); | ||
252 | + if (c == ' ' || c == '\t') { | ||
253 | + // convert tabs into spaces | ||
254 | + for (int j = 0; j < (c == ' ' ? 1 : TAB_WIDTH); j++) { | ||
255 | + if (convertSpace && convertBreakAndSpace) { | ||
256 | + buff.append(" "); | ||
257 | + } else { | ||
258 | + buff.append(' '); | ||
259 | + convertSpace = true; | ||
260 | + } | ||
261 | + } | ||
262 | + continue; | ||
263 | + } | ||
264 | + convertSpace = false; | ||
265 | + switch (c) { | ||
266 | + case '$': | ||
267 | + // so that ${ } in the text is interpreted correctly | ||
268 | + buff.append("$"); | ||
269 | + break; | ||
270 | + case '<': | ||
271 | + buff.append("<"); | ||
272 | + break; | ||
273 | + case '>': | ||
274 | + buff.append(">"); | ||
275 | + break; | ||
276 | + case '&': | ||
277 | + buff.append("&"); | ||
278 | + break; | ||
279 | + case '"': | ||
280 | + buff.append("""); | ||
281 | + break; | ||
282 | + case '\'': | ||
283 | + buff.append("'"); | ||
284 | + break; | ||
285 | + case '\n': | ||
286 | + if (convertBreakAndSpace) { | ||
287 | + buff.append("<br />"); | ||
288 | + convertSpace = true; | ||
289 | + } else { | ||
290 | + buff.append(c); | ||
291 | + } | ||
292 | + break; | ||
293 | + default: | ||
294 | + if (c >= 128) { | ||
295 | + buff.append("&#").append((int) c).append(';'); | ||
296 | + } else { | ||
297 | + buff.append(c); | ||
298 | + } | ||
299 | + break; | ||
300 | + } | ||
301 | + } | ||
302 | + return buff.toString(); | ||
303 | + } | ||
304 | + | ||
305 | + /** | ||
306 | + * Escape text as a the javascript string. | ||
307 | + * | ||
308 | + * @param s the text | ||
309 | + * @return the javascript string | ||
310 | + */ | ||
311 | + static String escapeJavaScript(String s) { | ||
312 | + if (s == null) { | ||
313 | + return null; | ||
314 | + } | ||
315 | + if (s.length() == 0) { | ||
316 | + return ""; | ||
317 | + } | ||
318 | + StringBuilder buff = new StringBuilder(s.length()); | ||
319 | + for (int i = 0; i < s.length(); i++) { | ||
320 | + char c = s.charAt(i); | ||
321 | + switch (c) { | ||
322 | + case '"': | ||
323 | + buff.append("\\\""); | ||
324 | + break; | ||
325 | + case '\'': | ||
326 | + buff.append("\\'"); | ||
327 | + break; | ||
328 | + case '\\': | ||
329 | + buff.append("\\\\"); | ||
330 | + break; | ||
331 | + case '\n': | ||
332 | + buff.append("\\n"); | ||
333 | + break; | ||
334 | + case '\r': | ||
335 | + buff.append("\\r"); | ||
336 | + break; | ||
337 | + case '\t': | ||
338 | + buff.append("\\t"); | ||
339 | + break; | ||
340 | + default: | ||
341 | + buff.append(c); | ||
342 | + break; | ||
343 | + } | ||
344 | + } | ||
345 | + return buff.toString(); | ||
346 | + } | ||
347 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
src/main/java/org/legrog/util/WebApp.java
0 → 100644
1 | +package org.legrog.util; | ||
2 | +// Code copied from org.h2.server.web.WebApp | ||
3 | +/* | ||
4 | + * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, | ||
5 | + * and the EPL 1.0 (http://h2database.com/html/license.html). | ||
6 | + * Initial Developer: H2 Group | ||
7 | + */ | ||
8 | +//package org.h2.server.web; | ||
9 | + | ||
10 | + import java.io.ByteArrayOutputStream; | ||
11 | + import java.io.PrintStream; | ||
12 | + import java.io.PrintWriter; | ||
13 | + import java.io.StringReader; | ||
14 | + import java.io.StringWriter; | ||
15 | + import java.lang.reflect.InvocationTargetException; | ||
16 | + import java.lang.reflect.Method; | ||
17 | + import java.math.BigDecimal; | ||
18 | + import java.sql.Connection; | ||
19 | + import java.sql.DatabaseMetaData; | ||
20 | + import java.sql.ParameterMetaData; | ||
21 | + import java.sql.PreparedStatement; | ||
22 | + import java.sql.ResultSet; | ||
23 | + import java.sql.ResultSetMetaData; | ||
24 | + import java.sql.SQLException; | ||
25 | + import java.sql.Statement; | ||
26 | + import java.sql.Types; | ||
27 | + import java.util.ArrayList; | ||
28 | + import java.util.Arrays; | ||
29 | + import java.util.Collections; | ||
30 | + import java.util.Comparator; | ||
31 | + import java.util.HashMap; | ||
32 | + import java.util.Iterator; | ||
33 | + import java.util.Locale; | ||
34 | + import java.util.Map; | ||
35 | + import java.util.Properties; | ||
36 | + import java.util.Random; | ||
37 | + import org.h2.api.ErrorCode; | ||
38 | + import org.h2.bnf.Bnf; | ||
39 | + import org.h2.bnf.context.DbColumn; | ||
40 | + import org.h2.bnf.context.DbContents; | ||
41 | + import org.h2.bnf.context.DbSchema; | ||
42 | + import org.h2.bnf.context.DbTableOrView; | ||
43 | + import org.h2.engine.Constants; | ||
44 | + import org.h2.engine.SysProperties; | ||
45 | + import org.h2.jdbc.JdbcSQLException; | ||
46 | + import org.h2.message.DbException; | ||
47 | + import org.h2.security.SHA256; | ||
48 | + import org.h2.tools.Backup; | ||
49 | + import org.h2.tools.ChangeFileEncryption; | ||
50 | + import org.h2.tools.ConvertTraceFile; | ||
51 | + import org.h2.tools.CreateCluster; | ||
52 | + import org.h2.tools.DeleteDbFiles; | ||
53 | + import org.h2.tools.Recover; | ||
54 | + import org.h2.tools.Restore; | ||
55 | + import org.h2.tools.RunScript; | ||
56 | + import org.h2.tools.Script; | ||
57 | + import org.h2.tools.SimpleResultSet; | ||
58 | + import org.h2.util.JdbcUtils; | ||
59 | + import org.h2.util.New; | ||
60 | + import org.h2.util.Profiler; | ||
61 | + import org.h2.util.ScriptReader; | ||
62 | + import org.h2.util.SortedProperties; | ||
63 | + import org.h2.util.StatementBuilder; | ||
64 | + import org.h2.util.StringUtils; | ||
65 | + import org.h2.util.Tool; | ||
66 | + import org.h2.util.Utils; | ||
67 | + | ||
68 | +/** | ||
69 | + * For each connection to a session, an object of this class is created. | ||
70 | + * This class is used by the H2 Console. | ||
71 | + */ | ||
72 | +public class WebApp { | ||
73 | + | ||
74 | + /** | ||
75 | + * The web server. | ||
76 | + */ | ||
77 | + protected final WebServer server; | ||
78 | + | ||
79 | + /** | ||
80 | + * The session. | ||
81 | + */ | ||
82 | + protected WebSession session; | ||
83 | + | ||
84 | + /** | ||
85 | + * The session attributes | ||
86 | + */ | ||
87 | + protected Properties attributes; | ||
88 | + | ||
89 | + /** | ||
90 | + * The mime type of the current response. | ||
91 | + */ | ||
92 | + protected String mimeType; | ||
93 | + | ||
94 | + /** | ||
95 | + * Whether the response can be cached. | ||
96 | + */ | ||
97 | + protected boolean cache; | ||
98 | + | ||
99 | + /** | ||
100 | + * Whether to close the connection. | ||
101 | + */ | ||
102 | + protected boolean stop; | ||
103 | + | ||
104 | + /** | ||
105 | + * The language in the HTTP header. | ||
106 | + */ | ||
107 | + protected String headerLanguage; | ||
108 | + | ||
109 | + private Profiler profiler; | ||
110 | + | ||
111 | + WebApp(WebServer server) { | ||
112 | + this.server = server; | ||
113 | + } | ||
114 | + | ||
115 | + /** | ||
116 | + * Set the web session and attributes. | ||
117 | + * | ||
118 | + * @param session the session | ||
119 | + * @param attributes the attributes | ||
120 | + */ | ||
121 | + void setSession(WebSession session, Properties attributes) { | ||
122 | + this.session = session; | ||
123 | + this.attributes = attributes; | ||
124 | + } | ||
125 | + | ||
126 | + /** | ||
127 | + * Process an HTTP request. | ||
128 | + * | ||
129 | + * @param file the file that was requested | ||
130 | + * @param hostAddr the host address | ||
131 | + * @return the name of the file to return to the client | ||
132 | + */ | ||
133 | + String processRequest(String file, String hostAddr) { | ||
134 | + int index = file.lastIndexOf('.'); | ||
135 | + String suffix; | ||
136 | + if (index >= 0) { | ||
137 | + suffix = file.substring(index + 1); | ||
138 | + } else { | ||
139 | + suffix = ""; | ||
140 | + } | ||
141 | + if ("ico".equals(suffix)) { | ||
142 | + mimeType = "image/x-icon"; | ||
143 | + cache = true; | ||
144 | + } else if ("gif".equals(suffix)) { | ||
145 | + mimeType = "image/gif"; | ||
146 | + cache = true; | ||
147 | + } else if ("css".equals(suffix)) { | ||
148 | + cache = true; | ||
149 | + mimeType = "text/css"; | ||
150 | + } else if ("html".equals(suffix) || | ||
151 | + "do".equals(suffix) || | ||
152 | + "jsp".equals(suffix)) { | ||
153 | + cache = false; | ||
154 | + mimeType = "text/html"; | ||
155 | + if (session == null) { | ||
156 | + session = server.createNewSession(hostAddr); | ||
157 | + if (!"notAllowed.jsp".equals(file)) { | ||
158 | + file = "index.do"; | ||
159 | + } | ||
160 | + } | ||
161 | + } else if ("js".equals(suffix)) { | ||
162 | + cache = true; | ||
163 | + mimeType = "text/javascript"; | ||
164 | + } else { | ||
165 | + cache = true; | ||
166 | + mimeType = "application/octet-stream"; | ||
167 | + } | ||
168 | + trace("mimeType=" + mimeType); | ||
169 | + trace(file); | ||
170 | + if (file.endsWith(".do")) { | ||
171 | + file = process(file); | ||
172 | + } | ||
173 | + return file; | ||
174 | + } | ||
175 | + | ||
176 | + private static String getComboBox(String[] elements, String selected) { | ||
177 | + StringBuilder buff = new StringBuilder(); | ||
178 | + for (String value : elements) { | ||
179 | + buff.append("<option value=\""). | ||
180 | + append(PageParser.escapeHtmlData(value)). | ||
181 | + append('\"'); | ||
182 | + if (value.equals(selected)) { | ||
183 | + buff.append(" selected"); | ||
184 | + } | ||
185 | + buff.append('>'). | ||
186 | + append(PageParser.escapeHtml(value)). | ||
187 | + append("</option>"); | ||
188 | + } | ||
189 | + return buff.toString(); | ||
190 | + } | ||
191 | + | ||
192 | + private static String getComboBox(String[][] elements, String selected) { | ||
193 | + StringBuilder buff = new StringBuilder(); | ||
194 | + for (String[] n : elements) { | ||
195 | + buff.append("<option value=\""). | ||
196 | + append(PageParser.escapeHtmlData(n[0])). | ||
197 | + append('\"'); | ||
198 | + if (n[0].equals(selected)) { | ||
199 | + buff.append(" selected"); | ||
200 | + } | ||
201 | + buff.append('>'). | ||
202 | + append(PageParser.escapeHtml(n[1])). | ||
203 | + append("</option>"); | ||
204 | + } | ||
205 | + return buff.toString(); | ||
206 | + } | ||
207 | + | ||
208 | + private String process(String file) { | ||
209 | + trace("process " + file); | ||
210 | + while (file.endsWith(".do")) { | ||
211 | + if ("login.do".equals(file)) { | ||
212 | + file = login(); | ||
213 | + } else if ("index.do".equals(file)) { | ||
214 | + file = index(); | ||
215 | + } else if ("logout.do".equals(file)) { | ||
216 | + file = logout(); | ||
217 | + } else if ("settingRemove.do".equals(file)) { | ||
218 | + file = settingRemove(); | ||
219 | + } else if ("settingSave.do".equals(file)) { | ||
220 | + file = settingSave(); | ||
221 | + } else if ("test.do".equals(file)) { | ||
222 | + file = test(); | ||
223 | + } else if ("query.do".equals(file)) { | ||
224 | + file = query(); | ||
225 | + } else if ("tables.do".equals(file)) { | ||
226 | + file = tables(); | ||
227 | + } else if ("editResult.do".equals(file)) { | ||
228 | + file = editResult(); | ||
229 | + } else if ("getHistory.do".equals(file)) { | ||
230 | + file = getHistory(); | ||
231 | + } else if ("admin.do".equals(file)) { | ||
232 | + file = admin(); | ||
233 | + } else if ("adminSave.do".equals(file)) { | ||
234 | + file = adminSave(); | ||
235 | + } else if ("adminStartTranslate.do".equals(file)) { | ||
236 | + file = adminStartTranslate(); | ||
237 | + } else if ("adminShutdown.do".equals(file)) { | ||
238 | + file = adminShutdown(); | ||
239 | + } else if ("autoCompleteList.do".equals(file)) { | ||
240 | + file = autoCompleteList(); | ||
241 | + } else if ("tools.do".equals(file)) { | ||
242 | + file = tools(); | ||
243 | + } else { | ||
244 | + file = "error.jsp"; | ||
245 | + } | ||
246 | + } | ||
247 | + trace("return " + file); | ||
248 | + return file; | ||
249 | + } | ||
250 | + | ||
251 | + private String autoCompleteList() { | ||
252 | + String query = (String) attributes.get("query"); | ||
253 | + boolean lowercase = false; | ||
254 | + if (query.trim().length() > 0 && | ||
255 | + Character.isLowerCase(query.trim().charAt(0))) { | ||
256 | + lowercase = true; | ||
257 | + } | ||
258 | + try { | ||
259 | + String sql = query; | ||
260 | + if (sql.endsWith(";")) { | ||
261 | + sql += " "; | ||
262 | + } | ||
263 | + ScriptReader reader = new ScriptReader(new StringReader(sql)); | ||
264 | + reader.setSkipRemarks(true); | ||
265 | + String lastSql = ""; | ||
266 | + while (true) { | ||
267 | + String n = reader.readStatement(); | ||
268 | + if (n == null) { | ||
269 | + break; | ||
270 | + } | ||
271 | + lastSql = n; | ||
272 | + } | ||
273 | + String result = ""; | ||
274 | + if (reader.isInsideRemark()) { | ||
275 | + if (reader.isBlockRemark()) { | ||
276 | + result = "1#(End Remark)# */\n" + result; | ||
277 | + } else { | ||
278 | + result = "1#(Newline)#\n" + result; | ||
279 | + } | ||
280 | + } else { | ||
281 | + sql = lastSql; | ||
282 | + while (sql.length() > 0 && sql.charAt(0) <= ' ') { | ||
283 | + sql = sql.substring(1); | ||
284 | + } | ||
285 | + if (sql.trim().length() > 0 && Character.isLowerCase(sql.trim().charAt(0))) { | ||
286 | + lowercase = true; | ||
287 | + } | ||
288 | + Bnf bnf = session.getBnf(); | ||
289 | + if (bnf == null) { | ||
290 | + return "autoCompleteList.jsp"; | ||
291 | + } | ||
292 | + HashMap<String, String> map = bnf.getNextTokenList(sql); | ||
293 | + String space = ""; | ||
294 | + if (sql.length() > 0) { | ||
295 | + char last = sql.charAt(sql.length() - 1); | ||
296 | + if (!Character.isWhitespace(last) && (last != '.' && | ||
297 | + last >= ' ' && last != '\'' && last != '"')) { | ||
298 | + space = " "; | ||
299 | + } | ||
300 | + } | ||
301 | + ArrayList<String> list = New.arrayList(map.size()); | ||
302 | + for (Map.Entry<String, String> entry : map.entrySet()) { | ||
303 | + String key = entry.getKey(); | ||
304 | + String value = entry.getValue(); | ||
305 | + String type = "" + key.charAt(0); | ||
306 | + if (Integer.parseInt(type) > 2) { | ||
307 | + continue; | ||
308 | + } | ||
309 | + key = key.substring(2); | ||
310 | + if (Character.isLetter(key.charAt(0)) && lowercase) { | ||
311 | + key = StringUtils.toLowerEnglish(key); | ||
312 | + value = StringUtils.toLowerEnglish(value); | ||
313 | + } | ||
314 | + if (key.equals(value) && !".".equals(value)) { | ||
315 | + value = space + value; | ||
316 | + } | ||
317 | + key = StringUtils.urlEncode(key); | ||
318 | + key = StringUtils.replaceAll(key, "+", " "); | ||
319 | + value = StringUtils.urlEncode(value); | ||
320 | + value = StringUtils.replaceAll(value, "+", " "); | ||
321 | + list.add(type + "#" + key + "#" + value); | ||
322 | + } | ||
323 | + Collections.sort(list); | ||
324 | + if (query.endsWith("\n") || query.trim().endsWith(";")) { | ||
325 | + list.add(0, "1#(Newline)#\n"); | ||
326 | + } | ||
327 | + StatementBuilder buff = new StatementBuilder(); | ||
328 | + for (String s : list) { | ||
329 | + buff.appendExceptFirst("|"); | ||
330 | + buff.append(s); | ||
331 | + } | ||
332 | + result = buff.toString(); | ||
333 | + } | ||
334 | + session.put("autoCompleteList", result); | ||
335 | + } catch (Throwable e) { | ||
336 | + server.traceError(e); | ||
337 | + } | ||
338 | + return "autoCompleteList.jsp"; | ||
339 | + } | ||
340 | + | ||
341 | + private String admin() { | ||
342 | + session.put("port", "" + server.getPort()); | ||
343 | + session.put("allowOthers", "" + server.getAllowOthers()); | ||
344 | + session.put("ssl", String.valueOf(server.getSSL())); | ||
345 | + session.put("sessions", server.getSessions()); | ||
346 | + return "admin.jsp"; | ||
347 | + } | ||
348 | + | ||
349 | + private String adminSave() { | ||
350 | + try { | ||
351 | + Properties prop = new SortedProperties(); | ||
352 | + int port = Integer.decode((String) attributes.get("port")); | ||
353 | + prop.setProperty("webPort", String.valueOf(port)); | ||
354 | + server.setPort(port); | ||
355 | + boolean allowOthers = Boolean.parseBoolean( | ||
356 | + (String) attributes.get("allowOthers")); | ||
357 | + prop.setProperty("webAllowOthers", String.valueOf(allowOthers)); | ||
358 | + server.setAllowOthers(allowOthers); | ||
359 | + boolean ssl = Boolean.parseBoolean( | ||
360 | + (String) attributes.get("ssl")); | ||
361 | + prop.setProperty("webSSL", String.valueOf(ssl)); | ||
362 | + server.setSSL(ssl); | ||
363 | + server.saveProperties(prop); | ||
364 | + } catch (Exception e) { | ||
365 | + trace(e.toString()); | ||
366 | + } | ||
367 | + return admin(); | ||
368 | + } | ||
369 | + | ||
370 | + private String tools() { | ||
371 | + try { | ||
372 | + String toolName = (String) attributes.get("tool"); | ||
373 | + session.put("tool", toolName); | ||
374 | + String args = (String) attributes.get("args"); | ||
375 | + String[] argList = StringUtils.arraySplit(args, ',', false); | ||
376 | + Tool tool = null; | ||
377 | + if ("Backup".equals(toolName)) { | ||
378 | + tool = new Backup(); | ||
379 | + } else if ("Restore".equals(toolName)) { | ||
380 | + tool = new Restore(); | ||
381 | + } else if ("Recover".equals(toolName)) { | ||
382 | + tool = new Recover(); | ||
383 | + } else if ("DeleteDbFiles".equals(toolName)) { | ||
384 | + tool = new DeleteDbFiles(); | ||
385 | + } else if ("ChangeFileEncryption".equals(toolName)) { | ||
386 | + tool = new ChangeFileEncryption(); | ||
387 | + } else if ("Script".equals(toolName)) { | ||
388 | + tool = new Script(); | ||
389 | + } else if ("RunScript".equals(toolName)) { | ||
390 | + tool = new RunScript(); | ||
391 | + } else if ("ConvertTraceFile".equals(toolName)) { | ||
392 | + tool = new ConvertTraceFile(); | ||
393 | + } else if ("CreateCluster".equals(toolName)) { | ||
394 | + tool = new CreateCluster(); | ||
395 | + } else { | ||
396 | + throw DbException.throwInternalError(toolName); | ||
397 | + } | ||
398 | + ByteArrayOutputStream outBuff = new ByteArrayOutputStream(); | ||
399 | + PrintStream out = new PrintStream(outBuff, false, "UTF-8"); | ||
400 | + tool.setOut(out); | ||
401 | + try { | ||
402 | + tool.runTool(argList); | ||
403 | + out.flush(); | ||
404 | + String o = new String(outBuff.toByteArray(), Constants.UTF8); | ||
405 | + String result = PageParser.escapeHtml(o); | ||
406 | + session.put("toolResult", result); | ||
407 | + } catch (Exception e) { | ||
408 | + session.put("toolResult", getStackTrace(0, e, true)); | ||
409 | + } | ||
410 | + } catch (Exception e) { | ||
411 | + server.traceError(e); | ||
412 | + } | ||
413 | + return "tools.jsp"; | ||
414 | + } | ||
415 | + | ||
416 | + private String adminStartTranslate() { | ||
417 | + Map<?, ?> p = Map.class.cast(session.map.get("text")); | ||
418 | + @SuppressWarnings("unchecked") | ||
419 | + Map<Object, Object> p2 = (Map<Object, Object>) p; | ||
420 | + String file = server.startTranslate(p2); | ||
421 | + session.put("translationFile", file); | ||
422 | + return "helpTranslate.jsp"; | ||
423 | + } | ||
424 | + | ||
425 | + /** | ||
426 | + * Stop the application and the server. | ||
427 | + * | ||
428 | + * @return the page to display | ||
429 | + */ | ||
430 | + protected String adminShutdown() { | ||
431 | + server.shutdown(); | ||
432 | + return "admin.jsp"; | ||
433 | + } | ||
434 | + | ||
435 | + private String index() { | ||
436 | + String[][] languageArray = WebServer.LANGUAGES; | ||
437 | + String language = (String) attributes.get("language"); | ||
438 | + Locale locale = session.locale; | ||
439 | + if (language != null) { | ||
440 | + if (locale == null || !StringUtils.toLowerEnglish( | ||
441 | + locale.getLanguage()).equals(language)) { | ||
442 | + locale = new Locale(language, ""); | ||
443 | + server.readTranslations(session, locale.getLanguage()); | ||
444 | + session.put("language", language); | ||
445 | + session.locale = locale; | ||
446 | + } | ||
447 | + } else { | ||
448 | + language = (String) session.get("language"); | ||
449 | + } | ||
450 | + if (language == null) { | ||
451 | + // if the language is not yet known | ||
452 | + // use the last header | ||
453 | + language = headerLanguage; | ||
454 | + } | ||
455 | + session.put("languageCombo", getComboBox(languageArray, language)); | ||
456 | + String[] settingNames = server.getSettingNames(); | ||
457 | + String setting = attributes.getProperty("setting"); | ||
458 | + if (setting == null && settingNames.length > 0) { | ||
459 | + setting = settingNames[0]; | ||
460 | + } | ||
461 | + String combobox = getComboBox(settingNames, setting); | ||
462 | + session.put("settingsList", combobox); | ||
463 | + ConnectionInfo info = server.getSetting(setting); | ||
464 | + if (info == null) { | ||
465 | + info = new ConnectionInfo(); | ||
466 | + } | ||
467 | + session.put("setting", PageParser.escapeHtmlData(setting)); | ||
468 | + session.put("name", PageParser.escapeHtmlData(setting)); | ||
469 | + session.put("driver", PageParser.escapeHtmlData(info.driver)); | ||
470 | + session.put("url", PageParser.escapeHtmlData(info.url)); | ||
471 | + session.put("user", PageParser.escapeHtmlData(info.user)); | ||
472 | + return "index.jsp"; | ||
473 | + } | ||
474 | + | ||
475 | + private String getHistory() { | ||
476 | + int id = Integer.parseInt(attributes.getProperty("id")); | ||
477 | + String sql = session.getCommand(id); | ||
478 | + session.put("query", PageParser.escapeHtmlData(sql)); | ||
479 | + return "query.jsp"; | ||
480 | + } | ||
481 | + | ||
482 | + private static int addColumns(boolean mainSchema, DbTableOrView table, | ||
483 | + StringBuilder buff, int treeIndex, boolean showColumnTypes, | ||
484 | + StringBuilder columnsBuffer) { | ||
485 | + DbColumn[] columns = table.getColumns(); | ||
486 | + for (int i = 0; columns != null && i < columns.length; i++) { | ||
487 | + DbColumn column = columns[i]; | ||
488 | + if (columnsBuffer.length() > 0) { | ||
489 | + columnsBuffer.append(' '); | ||
490 | + } | ||
491 | + columnsBuffer.append(column.getName()); | ||
492 | + String col = escapeIdentifier(column.getName()); | ||
493 | + String level = mainSchema ? ", 1, 1" : ", 2, 2"; | ||
494 | + buff.append("setNode(" + treeIndex + level + ", 'column', '" + | ||
495 | + PageParser.escapeJavaScript(column.getName()) + | ||
496 | + "', 'javascript:ins(\\'" + col + "\\')');\n"); | ||
497 | + treeIndex++; | ||
498 | + if (mainSchema && showColumnTypes) { | ||
499 | + buff.append("setNode(" + treeIndex + ", 2, 2, 'type', '" + | ||
500 | + PageParser.escapeJavaScript(column.getDataType()) + | ||
501 | + "', null);\n"); | ||
502 | + treeIndex++; | ||
503 | + } | ||
504 | + } | ||
505 | + return treeIndex; | ||
506 | + } | ||
507 | + | ||
508 | + private static String escapeIdentifier(String name) { | ||
509 | + return StringUtils.urlEncode( | ||
510 | + PageParser.escapeJavaScript(name)).replace('+', ' '); | ||
511 | + } | ||
512 | + | ||
513 | + /** | ||
514 | + * This class represents index information for the GUI. | ||
515 | + */ | ||
516 | + static class IndexInfo { | ||
517 | + | ||
518 | + /** | ||
519 | + * The index name. | ||
520 | + */ | ||
521 | + String name; | ||
522 | + | ||
523 | + /** | ||
524 | + * The index type name. | ||
525 | + */ | ||
526 | + String type; | ||
527 | + | ||
528 | + /** | ||
529 | + * The indexed columns. | ||
530 | + */ | ||
531 | + String columns; | ||
532 | + } | ||
533 | + | ||
534 | + private static int addIndexes(boolean mainSchema, DatabaseMetaData meta, | ||
535 | + String table, String schema, StringBuilder buff, int treeIndex) | ||
536 | + throws SQLException { | ||
537 | + ResultSet rs; | ||
538 | + try { | ||
539 | + rs = meta.getIndexInfo(null, schema, table, false, true); | ||
540 | + } catch (SQLException e) { | ||
541 | + // SQLite | ||
542 | + return treeIndex; | ||
543 | + } | ||
544 | + HashMap<String, IndexInfo> indexMap = New.hashMap(); | ||
545 | + while (rs.next()) { | ||
546 | + String name = rs.getString("INDEX_NAME"); | ||
547 | + IndexInfo info = indexMap.get(name); | ||
548 | + if (info == null) { | ||
549 | + int t = rs.getInt("TYPE"); | ||
550 | + String type; | ||
551 | + if (t == DatabaseMetaData.tableIndexClustered) { | ||
552 | + type = ""; | ||
553 | + } else if (t == DatabaseMetaData.tableIndexHashed) { | ||
554 | + type = " (${text.tree.hashed})"; | ||
555 | + } else if (t == DatabaseMetaData.tableIndexOther) { | ||
556 | + type = ""; | ||
557 | + } else { | ||
558 | + type = null; | ||
559 | + } | ||
560 | + if (name != null && type != null) { | ||
561 | + info = new IndexInfo(); | ||
562 | + info.name = name; | ||
563 | + type = (rs.getBoolean("NON_UNIQUE") ? | ||
564 | + "${text.tree.nonUnique}" : "${text.tree.unique}") + type; | ||
565 | + info.type = type; | ||
566 | + info.columns = rs.getString("COLUMN_NAME"); | ||
567 | + indexMap.put(name, info); | ||
568 | + } | ||
569 | + } else { | ||
570 | + info.columns += ", " + rs.getString("COLUMN_NAME"); | ||
571 | + } | ||
572 | + } | ||
573 | + rs.close(); | ||
574 | + if (indexMap.size() > 0) { | ||
575 | + String level = mainSchema ? ", 1, 1" : ", 2, 1"; | ||
576 | + String levelIndex = mainSchema ? ", 2, 1" : ", 3, 1"; | ||
577 | + String levelColumnType = mainSchema ? ", 3, 2" : ", 4, 2"; | ||
578 | + buff.append("setNode(" + treeIndex + level + | ||
579 | + ", 'index_az', '${text.tree.indexes}', null);\n"); | ||
580 | + treeIndex++; | ||
581 | + for (IndexInfo info : indexMap.values()) { | ||
582 | + buff.append("setNode(" + treeIndex + levelIndex + | ||
583 | + ", 'index', '" + | ||
584 | + PageParser.escapeJavaScript(info.name) + "', null);\n"); | ||
585 | + treeIndex++; | ||
586 | + buff.append("setNode(" + treeIndex + levelColumnType + | ||
587 | + ", 'type', '" + info.type + "', null);\n"); | ||
588 | + treeIndex++; | ||
589 | + buff.append("setNode(" + treeIndex + levelColumnType + | ||
590 | + ", 'type', '" + | ||
591 | + PageParser.escapeJavaScript(info.columns) + | ||
592 | + "', null);\n"); | ||
593 | + treeIndex++; | ||
594 | + } | ||
595 | + } | ||
596 | + return treeIndex; | ||
597 | + } | ||
598 | + | ||
599 | + private int addTablesAndViews(DbSchema schema, boolean mainSchema, | ||
600 | + StringBuilder buff, int treeIndex) throws SQLException { | ||
601 | + if (schema == null) { | ||
602 | + return treeIndex; | ||
603 | + } | ||
604 | + Connection conn = session.getConnection(); | ||
605 | + DatabaseMetaData meta = session.getMetaData(); | ||
606 | + int level = mainSchema ? 0 : 1; | ||
607 | + boolean showColumns = mainSchema || !schema.isSystem; | ||
608 | + String indentation = ", " + level + ", " + (showColumns ? "1" : "2") + ", "; | ||
609 | + String indentNode = ", " + (level + 1) + ", 2, "; | ||
610 | + DbTableOrView[] tables = schema.getTables(); | ||
611 | + if (tables == null) { | ||
612 | + return treeIndex; | ||
613 | + } | ||
614 | + boolean isOracle = schema.getContents().isOracle(); | ||
615 | + boolean notManyTables = tables.length < SysProperties.CONSOLE_MAX_TABLES_LIST_INDEXES; | ||
616 | + for (DbTableOrView table : tables) { | ||
617 | + if (table.isView()) { | ||
618 | + continue; | ||
619 | + } | ||
620 | + int tableId = treeIndex; | ||
621 | + String tab = table.getQuotedName(); | ||
622 | + if (!mainSchema) { | ||
623 | + tab = schema.quotedName + "." + tab; | ||
624 | + } | ||
625 | + tab = escapeIdentifier(tab); | ||
626 | + buff.append("setNode(" + treeIndex + indentation + " 'table', '" + | ||
627 | + PageParser.escapeJavaScript(table.getName()) + | ||
628 | + "', 'javascript:ins(\\'" + tab + "\\',true)');\n"); | ||
629 | + treeIndex++; | ||
630 | + if (mainSchema || showColumns) { | ||
631 | + StringBuilder columnsBuffer = new StringBuilder(); | ||
632 | + treeIndex = addColumns(mainSchema, table, buff, treeIndex, | ||
633 | + notManyTables, columnsBuffer); | ||
634 | + if (!isOracle && notManyTables) { | ||
635 | + treeIndex = addIndexes(mainSchema, meta, table.getName(), | ||
636 | + schema.name, buff, treeIndex); | ||
637 | + } | ||
638 | + buff.append("addTable('" + | ||
639 | + PageParser.escapeJavaScript(table.getName()) + "', '" + | ||
640 | + PageParser.escapeJavaScript(columnsBuffer.toString()) + | ||
641 | + "', " + tableId + ");\n"); | ||
642 | + } | ||
643 | + } | ||
644 | + tables = schema.getTables(); | ||
645 | + for (DbTableOrView view : tables) { | ||
646 | + if (!view.isView()) { | ||
647 | + continue; | ||
648 | + } | ||
649 | + int tableId = treeIndex; | ||
650 | + String tab = view.getQuotedName(); | ||
651 | + if (!mainSchema) { | ||
652 | + tab = view.getSchema().quotedName + "." + tab; | ||
653 | + } | ||
654 | + tab = escapeIdentifier(tab); | ||
655 | + buff.append("setNode(" + treeIndex + indentation + " 'view', '" + | ||
656 | + PageParser.escapeJavaScript(view.getName()) + | ||
657 | + "', 'javascript:ins(\\'" + tab + "\\',true)');\n"); | ||
658 | + treeIndex++; | ||
659 | + if (mainSchema) { | ||
660 | + StringBuilder columnsBuffer = new StringBuilder(); | ||
661 | + treeIndex = addColumns(mainSchema, view, buff, | ||
662 | + treeIndex, notManyTables, columnsBuffer); | ||
663 | + if (schema.getContents().isH2()) { | ||
664 | + | ||
665 | + try (PreparedStatement prep = conn.prepareStatement("SELECT * FROM " + | ||
666 | + "INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=?")) { | ||
667 | + prep.setString(1, view.getName()); | ||
668 | + ResultSet rs = prep.executeQuery(); | ||
669 | + if (rs.next()) { | ||
670 | + String sql = rs.getString("SQL"); | ||
671 | + buff.append("setNode(" + treeIndex + indentNode + | ||
672 | + " 'type', '" + | ||
673 | + PageParser.escapeJavaScript(sql) + | ||
674 | + "', null);\n"); | ||
675 | + treeIndex++; | ||
676 | + } | ||
677 | + rs.close(); | ||
678 | + } | ||
679 | + } | ||
680 | + buff.append("addTable('" + | ||
681 | + PageParser.escapeJavaScript(view.getName()) + "', '" + | ||
682 | + PageParser.escapeJavaScript(columnsBuffer.toString()) + | ||
683 | + "', " + tableId + ");\n"); | ||
684 | + } | ||
685 | + } | ||
686 | + return treeIndex; | ||
687 | + } | ||
688 | + | ||
689 | + private String tables() { | ||
690 | + DbContents contents = session.getContents(); | ||
691 | + boolean isH2 = false; | ||
692 | + try { | ||
693 | + String url = (String) session.get("url"); | ||
694 | + Connection conn = session.getConnection(); | ||
695 | + contents.readContents(url, conn); | ||
696 | + session.loadBnf(); | ||
697 | + isH2 = contents.isH2(); | ||
698 | + | ||
699 | + StringBuilder buff = new StringBuilder(); | ||
700 | + buff.append("setNode(0, 0, 0, 'database', '" + PageParser.escapeJavaScript(url) | ||
701 | + + "', null);\n"); | ||
702 | + int treeIndex = 1; | ||
703 | + | ||
704 | + DbSchema defaultSchema = contents.getDefaultSchema(); | ||
705 | + treeIndex = addTablesAndViews(defaultSchema, true, buff, treeIndex); | ||
706 | + DbSchema[] schemas = contents.getSchemas(); | ||
707 | + for (DbSchema schema : schemas) { | ||
708 | + if (schema == defaultSchema || schema == null) { | ||
709 | + continue; | ||
710 | + } | ||
711 | + buff.append("setNode(" + treeIndex + ", 0, 1, 'folder', '" + | ||
712 | + PageParser.escapeJavaScript(schema.name) + | ||
713 | + "', null);\n"); | ||
714 | + treeIndex++; | ||
715 | + treeIndex = addTablesAndViews(schema, false, buff, treeIndex); | ||
716 | + } | ||
717 | + if (isH2) { | ||
718 | + try (Statement stat = conn.createStatement()) { | ||
719 | + ResultSet rs = stat.executeQuery("SELECT * FROM " + | ||
720 | + "INFORMATION_SCHEMA.SEQUENCES ORDER BY SEQUENCE_NAME"); | ||
721 | + for (int i = 0; rs.next(); i++) { | ||
722 | + if (i == 0) { | ||
723 | + buff.append("setNode(" + treeIndex + | ||
724 | + ", 0, 1, 'sequences', '${text.tree.sequences}', null);\n"); | ||
725 | + treeIndex++; | ||
726 | + } | ||
727 | + String name = rs.getString("SEQUENCE_NAME"); | ||
728 | + String current = rs.getString("CURRENT_VALUE"); | ||
729 | + String increment = rs.getString("INCREMENT"); | ||
730 | + buff.append("setNode(" + treeIndex + | ||
731 | + ", 1, 1, 'sequence', '" + | ||
732 | + PageParser.escapeJavaScript(name) + | ||
733 | + "', null);\n"); | ||
734 | + treeIndex++; | ||
735 | + buff.append("setNode(" + treeIndex + | ||
736 | + ", 2, 2, 'type', '${text.tree.current}: " + | ||
737 | + PageParser.escapeJavaScript(current) + | ||
738 | + "', null);\n"); | ||
739 | + treeIndex++; | ||
740 | + if (!"1".equals(increment)) { | ||
741 | + buff.append("setNode(" + | ||
742 | + treeIndex + | ||
743 | + ", 2, 2, 'type', '${text.tree.increment}: " + | ||
744 | + PageParser.escapeJavaScript(increment) + | ||
745 | + "', null);\n"); | ||
746 | + treeIndex++; | ||
747 | + } | ||
748 | + } | ||
749 | + rs.close(); | ||
750 | + rs = stat.executeQuery("SELECT * FROM " + | ||
751 | + "INFORMATION_SCHEMA.USERS ORDER BY NAME"); | ||
752 | + for (int i = 0; rs.next(); i++) { | ||
753 | + if (i == 0) { | ||
754 | + buff.append("setNode(" + treeIndex + | ||
755 | + ", 0, 1, 'users', '${text.tree.users}', null);\n"); | ||
756 | + treeIndex++; | ||
757 | + } | ||
758 | + String name = rs.getString("NAME"); | ||
759 | + String admin = rs.getString("ADMIN"); | ||
760 | + buff.append("setNode(" + treeIndex + | ||
761 | + ", 1, 1, 'user', '" + | ||
762 | + PageParser.escapeJavaScript(name) + | ||
763 | + "', null);\n"); | ||
764 | + treeIndex++; | ||
765 | + if (admin.equalsIgnoreCase("TRUE")) { | ||
766 | + buff.append("setNode(" + treeIndex + | ||
767 | + ", 2, 2, 'type', '${text.tree.admin}', null);\n"); | ||
768 | + treeIndex++; | ||
769 | + } | ||
770 | + } | ||
771 | + rs.close(); | ||
772 | + } | ||
773 | + } | ||
774 | + DatabaseMetaData meta = session.getMetaData(); | ||
775 | + String version = meta.getDatabaseProductName() + " " + | ||
776 | + meta.getDatabaseProductVersion(); | ||
777 | + buff.append("setNode(" + treeIndex + ", 0, 0, 'info', '" + | ||
778 | + PageParser.escapeJavaScript(version) + "', null);\n"); | ||
779 | + buff.append("refreshQueryTables();"); | ||
780 | + session.put("tree", buff.toString()); | ||
781 | + } catch (Exception e) { | ||
782 | + session.put("tree", ""); | ||
783 | + session.put("error", getStackTrace(0, e, isH2)); | ||
784 | + } | ||
785 | + return "tables.jsp"; | ||
786 | + } | ||
787 | + | ||
788 | + private String getStackTrace(int id, Throwable e, boolean isH2) { | ||
789 | + try { | ||
790 | + StringWriter writer = new StringWriter(); | ||
791 | + e.printStackTrace(new PrintWriter(writer)); | ||
792 | + String stackTrace = writer.toString(); | ||
793 | + stackTrace = PageParser.escapeHtml(stackTrace); | ||
794 | + if (isH2) { | ||
795 | + stackTrace = linkToSource(stackTrace); | ||
796 | + } | ||
797 | + stackTrace = StringUtils.replaceAll(stackTrace, "\t", | ||
798 | + " "); | ||
799 | + String message = PageParser.escapeHtml(e.getMessage()); | ||
800 | + String error = "<a class=\"error\" href=\"#\" " + | ||
801 | + "onclick=\"var x=document.getElementById('st" + id + | ||
802 | + "').style;x.display=x.display==''?'none':'';\">" + message + | ||
803 | + "</a>"; | ||
804 | + if (e instanceof SQLException) { | ||
805 | + SQLException se = (SQLException) e; | ||
806 | + error += " " + se.getSQLState() + "/" + se.getErrorCode(); | ||
807 | + if (isH2) { | ||
808 | + int code = se.getErrorCode(); | ||
809 | + error += " <a href=\"http://h2database.com/javadoc/" + | ||
810 | + "org/h2/api/ErrorCode.html#c" + code + | ||
811 | + "\">(${text.a.help})</a>"; | ||
812 | + } | ||
813 | + } | ||
814 | + error += "<span style=\"display: none;\" id=\"st" + id + | ||
815 | + "\"><br />" + stackTrace + "</span>"; | ||
816 | + error = formatAsError(error); | ||
817 | + return error; | ||
818 | + } catch (OutOfMemoryError e2) { | ||
819 | + server.traceError(e); | ||
820 | + return e.toString(); | ||
821 | + } | ||
822 | + } | ||
823 | + | ||
824 | + private static String linkToSource(String s) { | ||
825 | + try { | ||
826 | + StringBuilder result = new StringBuilder(s.length()); | ||
827 | + int idx = s.indexOf("<br />"); | ||
828 | + result.append(s.substring(0, idx)); | ||
829 | + while (true) { | ||
830 | + int start = s.indexOf("org.h2.", idx); | ||
831 | + if (start < 0) { | ||
832 | + result.append(s.substring(idx)); | ||
833 | + break; | ||
834 | + } | ||
835 | + result.append(s.substring(idx, start)); | ||
836 | + int end = s.indexOf(')', start); | ||
837 | + if (end < 0) { | ||
838 | + result.append(s.substring(idx)); | ||
839 | + break; | ||
840 | + } | ||
841 | + String element = s.substring(start, end); | ||
842 | + int open = element.lastIndexOf('('); | ||
843 | + int dotMethod = element.lastIndexOf('.', open - 1); | ||
844 | + int dotClass = element.lastIndexOf('.', dotMethod - 1); | ||
845 | + String packageName = element.substring(0, dotClass); | ||
846 | + int colon = element.lastIndexOf(':'); | ||
847 | + String file = element.substring(open + 1, colon); | ||
848 | + String lineNumber = element.substring(colon + 1, element.length()); | ||
849 | + String fullFileName = packageName.replace('.', '/') + "/" + file; | ||
850 | + result.append("<a href=\"http://h2database.com/html/source.html?file="); | ||
851 | + result.append(fullFileName); | ||
852 | + result.append("&line="); | ||
853 | + result.append(lineNumber); | ||
854 | + result.append("&build="); | ||
855 | + result.append(Constants.BUILD_ID); | ||
856 | + result.append("\">"); | ||
857 | + result.append(element); | ||
858 | + result.append("</a>"); | ||
859 | + idx = end; | ||
860 | + } | ||
861 | + return result.toString(); | ||
862 | + } catch (Throwable t) { | ||
863 | + return s; | ||
864 | + } | ||
865 | + } | ||
866 | + | ||
867 | + private static String formatAsError(String s) { | ||
868 | + return "<div class=\"error\">" + s + "</div>"; | ||
869 | + } | ||
870 | + | ||
871 | + private String test() { | ||
872 | + String driver = attributes.getProperty("driver", ""); | ||
873 | + String url = attributes.getProperty("url", ""); | ||
874 | + String user = attributes.getProperty("user", ""); | ||
875 | + String password = attributes.getProperty("password", ""); | ||
876 | + session.put("driver", driver); | ||
877 | + session.put("url", url); | ||
878 | + session.put("user", user); | ||
879 | + boolean isH2 = url.startsWith("jdbc:h2:"); | ||
880 | + try { | ||
881 | + long start = System.currentTimeMillis(); | ||
882 | + String profOpen = "", profClose = ""; | ||
883 | + Profiler prof = new Profiler(); | ||
884 | + prof.startCollecting(); | ||
885 | + Connection conn; | ||
886 | + try { | ||
887 | + conn = server.getConnection(driver, url, user, password); | ||
888 | + } finally { | ||
889 | + prof.stopCollecting(); | ||
890 | + profOpen = prof.getTop(3); | ||
891 | + } | ||
892 | + prof = new Profiler(); | ||
893 | + prof.startCollecting(); | ||
894 | + try { | ||
895 | + JdbcUtils.closeSilently(conn); | ||
896 | + } finally { | ||
897 | + prof.stopCollecting(); | ||
898 | + profClose = prof.getTop(3); | ||
899 | + } | ||
900 | + long time = System.currentTimeMillis() - start; | ||
901 | + String success; | ||
902 | + if (time > 1000) { | ||
903 | + success = "<a class=\"error\" href=\"#\" " + | ||
904 | + "onclick=\"var x=document.getElementById('prof').style;x." + | ||
905 | + "display=x.display==''?'none':'';\">" + | ||
906 | + "${text.login.testSuccessful}</a>" + | ||
907 | + "<span style=\"display: none;\" id=\"prof\"><br />" + | ||
908 | + PageParser.escapeHtml(profOpen) + | ||
909 | + "<br />" + | ||
910 | + PageParser.escapeHtml(profClose) + | ||
911 | + "</span>"; | ||
912 | + } else { | ||
913 | + success = "${text.login.testSuccessful}"; | ||
914 | + } | ||
915 | + session.put("error", success); | ||
916 | + // session.put("error", "${text.login.testSuccessful}"); | ||
917 | + return "login.jsp"; | ||
918 | + } catch (Exception e) { | ||
919 | + session.put("error", getLoginError(e, isH2)); | ||
920 | + return "login.jsp"; | ||
921 | + } | ||
922 | + } | ||
923 | + | ||
924 | + /** | ||
925 | + * Get the formatted login error message. | ||
926 | + * | ||
927 | + * @param e the exception | ||
928 | + * @param isH2 if the current database is a H2 database | ||
929 | + * @return the formatted error message | ||
930 | + */ | ||
931 | + private String getLoginError(Exception e, boolean isH2) { | ||
932 | + if (e instanceof JdbcSQLException && | ||
933 | + ((JdbcSQLException) e).getErrorCode() == ErrorCode.CLASS_NOT_FOUND_1) { | ||
934 | + return "${text.login.driverNotFound}<br />" + getStackTrace(0, e, isH2); | ||
935 | + } | ||
936 | + return getStackTrace(0, e, isH2); | ||
937 | + } | ||
938 | + | ||
939 | + private String login() { | ||
940 | + String driver = attributes.getProperty("driver", ""); | ||
941 | + String url = attributes.getProperty("url", ""); | ||
942 | + String user = attributes.getProperty("user", ""); | ||
943 | + String password = attributes.getProperty("password", ""); | ||
944 | + session.put("autoCommit", "checked"); | ||
945 | + session.put("autoComplete", "1"); | ||
946 | + session.put("maxrows", "1000"); | ||
947 | + boolean isH2 = url.startsWith("jdbc:h2:"); | ||
948 | + try { | ||
949 | + Connection conn = server.getConnection(driver, url, user, password); | ||
950 | + session.setConnection(conn); | ||
951 | + session.put("url", url); | ||
952 | + session.put("user", user); | ||
953 | + session.remove("error"); | ||
954 | + settingSave(); | ||
955 | + return "frame.jsp"; | ||
956 | + } catch (Exception e) { | ||
957 | + session.put("error", getLoginError(e, isH2)); | ||
958 | + return "login.jsp"; | ||
959 | + } | ||
960 | + } | ||
961 | + | ||
962 | + private String logout() { | ||
963 | + try { | ||
964 | + Connection conn = session.getConnection(); | ||
965 | + session.setConnection(null); | ||
966 | + session.remove("conn"); | ||
967 | + session.remove("result"); | ||
968 | + session.remove("tables"); | ||
969 | + session.remove("user"); | ||
970 | + session.remove("tool"); | ||
971 | + if (conn != null) { | ||
972 | + if (session.getShutdownServerOnDisconnect()) { | ||
973 | + server.shutdown(); | ||
974 | + } else { | ||
975 | + conn.close(); | ||
976 | + } | ||
977 | + } | ||
978 | + } catch (Exception e) { | ||
979 | + trace(e.toString()); | ||
980 | + } | ||
981 | + return "index.do"; | ||
982 | + } | ||
983 | + | ||
984 | + private String query() { | ||
985 | + String sql = attributes.getProperty("sql").trim(); | ||
986 | + try { | ||
987 | + ScriptReader r = new ScriptReader(new StringReader(sql)); | ||
988 | + final ArrayList<String> list = New.arrayList(); | ||
989 | + while (true) { | ||
990 | + String s = r.readStatement(); | ||
991 | + if (s == null) { | ||
992 | + break; | ||
993 | + } | ||
994 | + list.add(s); | ||
995 | + } | ||
996 | + final Connection conn = session.getConnection(); | ||
997 | + if (SysProperties.CONSOLE_STREAM && server.getAllowChunked()) { | ||
998 | + String page = new String(server.getFile("result.jsp"), Constants.UTF8); | ||
999 | + int idx = page.indexOf("${result}"); | ||
1000 | + // the first element of the list is the header, the last the | ||
1001 | + // footer | ||
1002 | + list.add(0, page.substring(0, idx)); | ||
1003 | + list.add(page.substring(idx + "${result}".length())); | ||
1004 | + session.put("chunks", new Iterator<String>() { | ||
1005 | + private int i; | ||
1006 | + @Override | ||
1007 | + public boolean hasNext() { | ||
1008 | + return i < list.size(); | ||
1009 | + } | ||
1010 | + @Override | ||
1011 | + public String next() { | ||
1012 | + String s = list.get(i++); | ||
1013 | + if (i == 1 || i == list.size()) { | ||
1014 | + return s; | ||
1015 | + } | ||
1016 | + StringBuilder b = new StringBuilder(); | ||
1017 | + query(conn, s, i - 1, list.size() - 2, b); | ||
1018 | + return b.toString(); | ||
1019 | + } | ||
1020 | + @Override | ||
1021 | + public void remove() { | ||
1022 | + throw new UnsupportedOperationException(); | ||
1023 | + } | ||
1024 | + }); | ||
1025 | + return "result.jsp"; | ||
1026 | + } | ||
1027 | + String result; | ||
1028 | + StringBuilder buff = new StringBuilder(); | ||
1029 | + for (int i = 0; i < list.size(); i++) { | ||
1030 | + String s = list.get(i); | ||
1031 | + query(conn, s, i, list.size(), buff); | ||
1032 | + } | ||
1033 | + result = buff.toString(); | ||
1034 | + session.put("result", result); | ||
1035 | + } catch (Throwable e) { | ||
1036 | + session.put("result", getStackTrace(0, e, session.getContents().isH2())); | ||
1037 | + } | ||
1038 | + return "result.jsp"; | ||
1039 | + } | ||
1040 | + | ||
1041 | + /** | ||
1042 | + * Execute a query and append the result to the buffer. | ||
1043 | + * | ||
1044 | + * @param conn the connection | ||
1045 | + * @param s the statement | ||
1046 | + * @param i the index | ||
1047 | + * @param size the number of statements | ||
1048 | + * @param buff the target buffer | ||
1049 | + */ | ||
1050 | + void query(Connection conn, String s, int i, int size, StringBuilder buff) { | ||
1051 | + if (!(s.startsWith("@") && s.endsWith("."))) { | ||
1052 | + buff.append(PageParser.escapeHtml(s + ";")).append("<br />"); | ||
1053 | + } | ||
1054 | + boolean forceEdit = s.startsWith("@edit"); | ||
1055 | + buff.append(getResult(conn, i + 1, s, size == 1, forceEdit)). | ||
1056 | + append("<br />"); | ||
1057 | + } | ||
1058 | + | ||
1059 | + private String editResult() { | ||
1060 | + ResultSet rs = session.result; | ||
1061 | + int row = Integer.parseInt(attributes.getProperty("row")); | ||
1062 | + int op = Integer.parseInt(attributes.getProperty("op")); | ||
1063 | + String result = "", error = ""; | ||
1064 | + try { | ||
1065 | + if (op == 1) { | ||
1066 | + boolean insert = row < 0; | ||
1067 | + if (insert) { | ||
1068 | + rs.moveToInsertRow(); | ||
1069 | + } else { | ||
1070 | + rs.absolute(row); | ||
1071 | + } | ||
1072 | + for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) { | ||
1073 | + String x = attributes.getProperty("r" + row + "c" + (i + 1)); | ||
1074 | + unescapeData(x, rs, i + 1); | ||
1075 | + } | ||
1076 | + if (insert) { | ||
1077 | + rs.insertRow(); | ||
1078 | + } else { | ||
1079 | + rs.updateRow(); | ||
1080 | + } | ||
1081 | + } else if (op == 2) { | ||
1082 | + rs.absolute(row); | ||
1083 | + rs.deleteRow(); | ||
1084 | + } else if (op == 3) { | ||
1085 | + // cancel | ||
1086 | + } | ||
1087 | + } catch (Throwable e) { | ||
1088 | + result = "<br />" + getStackTrace(0, e, session.getContents().isH2()); | ||
1089 | + error = formatAsError(e.getMessage()); | ||
1090 | + } | ||
1091 | + String sql = "@edit " + (String) session.get("resultSetSQL"); | ||
1092 | + Connection conn = session.getConnection(); | ||
1093 | + result = error + getResult(conn, -1, sql, true, true) + result; | ||
1094 | + session.put("result", result); | ||
1095 | + return "result.jsp"; | ||
1096 | + } | ||
1097 | + | ||
1098 | + private ResultSet getMetaResultSet(Connection conn, String sql) | ||
1099 | + throws SQLException { | ||
1100 | + DatabaseMetaData meta = conn.getMetaData(); | ||
1101 | + if (isBuiltIn(sql, "@best_row_identifier")) { | ||
1102 | + String[] p = split(sql); | ||
1103 | + int scale = p[4] == null ? 0 : Integer.parseInt(p[4]); | ||
1104 | + boolean nullable = p[5] == null ? false : Boolean.parseBoolean(p[5]); | ||
1105 | + return meta.getBestRowIdentifier(p[1], p[2], p[3], scale, nullable); | ||
1106 | + } else if (isBuiltIn(sql, "@catalogs")) { | ||
1107 | + return meta.getCatalogs(); | ||
1108 | + } else if (isBuiltIn(sql, "@columns")) { | ||
1109 | + String[] p = split(sql); | ||
1110 | + return meta.getColumns(p[1], p[2], p[3], p[4]); | ||
1111 | + } else if (isBuiltIn(sql, "@column_privileges")) { | ||
1112 | + String[] p = split(sql); | ||
1113 | + return meta.getColumnPrivileges(p[1], p[2], p[3], p[4]); | ||
1114 | + } else if (isBuiltIn(sql, "@cross_references")) { | ||
1115 | + String[] p = split(sql); | ||
1116 | + return meta.getCrossReference(p[1], p[2], p[3], p[4], p[5], p[6]); | ||
1117 | + } else if (isBuiltIn(sql, "@exported_keys")) { | ||
1118 | + String[] p = split(sql); | ||
1119 | + return meta.getExportedKeys(p[1], p[2], p[3]); | ||
1120 | + } else if (isBuiltIn(sql, "@imported_keys")) { | ||
1121 | + String[] p = split(sql); | ||
1122 | + return meta.getImportedKeys(p[1], p[2], p[3]); | ||
1123 | + } else if (isBuiltIn(sql, "@index_info")) { | ||
1124 | + String[] p = split(sql); | ||
1125 | + boolean unique = p[4] == null ? false : Boolean.parseBoolean(p[4]); | ||
1126 | + boolean approx = p[5] == null ? false : Boolean.parseBoolean(p[5]); | ||
1127 | + return meta.getIndexInfo(p[1], p[2], p[3], unique, approx); | ||
1128 | + } else if (isBuiltIn(sql, "@primary_keys")) { | ||
1129 | + String[] p = split(sql); | ||
1130 | + return meta.getPrimaryKeys(p[1], p[2], p[3]); | ||
1131 | + } else if (isBuiltIn(sql, "@procedures")) { | ||
1132 | + String[] p = split(sql); | ||
1133 | + return meta.getProcedures(p[1], p[2], p[3]); | ||
1134 | + } else if (isBuiltIn(sql, "@procedure_columns")) { | ||
1135 | + String[] p = split(sql); | ||
1136 | + return meta.getProcedureColumns(p[1], p[2], p[3], p[4]); | ||
1137 | + } else if (isBuiltIn(sql, "@schemas")) { | ||
1138 | + return meta.getSchemas(); | ||
1139 | + } else if (isBuiltIn(sql, "@tables")) { | ||
1140 | + String[] p = split(sql); | ||
1141 | + String[] types = p[4] == null ? null : StringUtils.arraySplit(p[4], ',', false); | ||
1142 | + return meta.getTables(p[1], p[2], p[3], types); | ||
1143 | + } else if (isBuiltIn(sql, "@table_privileges")) { | ||
1144 | + String[] p = split(sql); | ||
1145 | + return meta.getTablePrivileges(p[1], p[2], p[3]); | ||
1146 | + } else if (isBuiltIn(sql, "@table_types")) { | ||
1147 | + return meta.getTableTypes(); | ||
1148 | + } else if (isBuiltIn(sql, "@type_info")) { | ||
1149 | + return meta.getTypeInfo(); | ||
1150 | + } else if (isBuiltIn(sql, "@udts")) { | ||
1151 | + String[] p = split(sql); | ||
1152 | + int[] types; | ||
1153 | + if (p[4] == null) { | ||
1154 | + types = null; | ||
1155 | + } else { | ||
1156 | + String[] t = StringUtils.arraySplit(p[4], ',', false); | ||
1157 | + types = new int[t.length]; | ||
1158 | + for (int i = 0; i < t.length; i++) { | ||
1159 | + types[i] = Integer.parseInt(t[i]); | ||
1160 | + } | ||
1161 | + } | ||
1162 | + return meta.getUDTs(p[1], p[2], p[3], types); | ||
1163 | + } else if (isBuiltIn(sql, "@version_columns")) { | ||
1164 | + String[] p = split(sql); | ||
1165 | + return meta.getVersionColumns(p[1], p[2], p[3]); | ||
1166 | + } else if (isBuiltIn(sql, "@memory")) { | ||
1167 | + SimpleResultSet rs = new SimpleResultSet(); | ||
1168 | + rs.addColumn("Type", Types.VARCHAR, 0, 0); | ||
1169 | + rs.addColumn("KB", Types.VARCHAR, 0, 0); | ||
1170 | + rs.addRow("Used Memory", "" + Utils.getMemoryUsed()); | ||
1171 | + rs.addRow("Free Memory", "" + Utils.getMemoryFree()); | ||
1172 | + return rs; | ||
1173 | + } else if (isBuiltIn(sql, "@info")) { | ||
1174 | + SimpleResultSet rs = new SimpleResultSet(); | ||
1175 | + rs.addColumn("KEY", Types.VARCHAR, 0, 0); | ||
1176 | + rs.addColumn("VALUE", Types.VARCHAR, 0, 0); | ||
1177 | + rs.addRow("conn.getCatalog", conn.getCatalog()); | ||
1178 | + rs.addRow("conn.getAutoCommit", "" + conn.getAutoCommit()); | ||
1179 | + rs.addRow("conn.getTransactionIsolation", "" + conn.getTransactionIsolation()); | ||
1180 | + rs.addRow("conn.getWarnings", "" + conn.getWarnings()); | ||
1181 | + String map; | ||
1182 | + try { | ||
1183 | + map = "" + conn.getTypeMap(); | ||
1184 | + } catch (SQLException e) { | ||
1185 | + map = e.toString(); | ||
1186 | + } | ||
1187 | + rs.addRow("conn.getTypeMap", "" + map); | ||
1188 | + rs.addRow("conn.isReadOnly", "" + conn.isReadOnly()); | ||
1189 | + rs.addRow("conn.getHoldability", "" + conn.getHoldability()); | ||
1190 | + addDatabaseMetaData(rs, meta); | ||
1191 | + return rs; | ||
1192 | + } else if (isBuiltIn(sql, "@attributes")) { | ||
1193 | + String[] p = split(sql); | ||
1194 | + return meta.getAttributes(p[1], p[2], p[3], p[4]); | ||
1195 | + } else if (isBuiltIn(sql, "@super_tables")) { | ||
1196 | + String[] p = split(sql); | ||
1197 | + return meta.getSuperTables(p[1], p[2], p[3]); | ||
1198 | + } else if (isBuiltIn(sql, "@super_types")) { | ||
1199 | + String[] p = split(sql); | ||
1200 | + return meta.getSuperTypes(p[1], p[2], p[3]); | ||
1201 | + } else if (isBuiltIn(sql, "@prof_stop")) { | ||
1202 | + if (profiler != null) { | ||
1203 | + profiler.stopCollecting(); | ||
1204 | + SimpleResultSet rs = new SimpleResultSet(); | ||
1205 | + rs.addColumn("Top Stack Trace(s)", Types.VARCHAR, 0, 0); | ||
1206 | + rs.addRow(profiler.getTop(3)); | ||
1207 | + profiler = null; | ||
1208 | + return rs; | ||
1209 | + } | ||
1210 | + } | ||
1211 | + return null; | ||
1212 | + } | ||
1213 | + | ||
1214 | + private static void addDatabaseMetaData(SimpleResultSet rs, | ||
1215 | + DatabaseMetaData meta) { | ||
1216 | + Method[] methods = DatabaseMetaData.class.getDeclaredMethods(); | ||
1217 | + Arrays.sort(methods, new Comparator<Method>() { | ||
1218 | + @Override | ||
1219 | + public int compare(Method o1, Method o2) { | ||
1220 | + return o1.toString().compareTo(o2.toString()); | ||
1221 | + } | ||
1222 | + }); | ||
1223 | + for (Method m : methods) { | ||
1224 | + if (m.getParameterTypes().length == 0) { | ||
1225 | + try { | ||
1226 | + Object o = m.invoke(meta); | ||
1227 | + rs.addRow("meta." + m.getName(), "" + o); | ||
1228 | + } catch (InvocationTargetException e) { | ||
1229 | + rs.addRow("meta." + m.getName(), e.getTargetException().toString()); | ||
1230 | + } catch (Exception e) { | ||
1231 | + rs.addRow("meta." + m.getName(), e.toString()); | ||
1232 | + } | ||
1233 | + } | ||
1234 | + } | ||
1235 | + } | ||
1236 | + | ||
1237 | + private static String[] split(String s) { | ||
1238 | + String[] list = new String[10]; | ||
1239 | + String[] t = StringUtils.arraySplit(s, ' ', true); | ||
1240 | + System.arraycopy(t, 0, list, 0, t.length); | ||
1241 | + for (int i = 0; i < list.length; i++) { | ||
1242 | + if ("null".equals(list[i])) { | ||
1243 | + list[i] = null; | ||
1244 | + } | ||
1245 | + } | ||
1246 | + return list; | ||
1247 | + } | ||
1248 | + | ||
1249 | + private int getMaxrows() { | ||
1250 | + String r = (String) session.get("maxrows"); | ||
1251 | + int maxrows = r == null ? 0 : Integer.parseInt(r); | ||
1252 | + return maxrows; | ||
1253 | + } | ||
1254 | + | ||
1255 | + private String getResult(Connection conn, int id, String sql, | ||
1256 | + boolean allowEdit, boolean forceEdit) { | ||
1257 | + try { | ||
1258 | + sql = sql.trim(); | ||
1259 | + StringBuilder buff = new StringBuilder(); | ||
1260 | + String sqlUpper = StringUtils.toUpperEnglish(sql); | ||
1261 | + if (sqlUpper.contains("CREATE") || | ||
1262 | + sqlUpper.contains("DROP") || | ||
1263 | + sqlUpper.contains("ALTER") || | ||
1264 | + sqlUpper.contains("RUNSCRIPT")) { | ||
1265 | + String sessionId = attributes.getProperty("jsessionid"); | ||
1266 | + buff.append("<script type=\"text/javascript\">" + | ||
1267 | + "parent['h2menu'].location='tables.do?jsessionid=" | ||
1268 | + + sessionId + "';</script>"); | ||
1269 | + } | ||
1270 | + Statement stat; | ||
1271 | + DbContents contents = session.getContents(); | ||
1272 | + if (forceEdit || (allowEdit && contents.isH2())) { | ||
1273 | + stat = conn.createStatement( | ||
1274 | + ResultSet.TYPE_SCROLL_INSENSITIVE, | ||
1275 | + ResultSet.CONCUR_UPDATABLE); | ||
1276 | + } else { | ||
1277 | + stat = conn.createStatement(); | ||
1278 | + } | ||
1279 | + ResultSet rs; | ||
1280 | + long time = System.currentTimeMillis(); | ||
1281 | + boolean metadata = false; | ||
1282 | + boolean generatedKeys = false; | ||
1283 | + boolean edit = false; | ||
1284 | + boolean list = false; | ||
1285 | + if (isBuiltIn(sql, "@autocommit_true")) { | ||
1286 | + conn.setAutoCommit(true); | ||
1287 | + return "${text.result.autoCommitOn}"; | ||
1288 | + } else if (isBuiltIn(sql, "@autocommit_false")) { | ||
1289 | + conn.setAutoCommit(false); | ||
1290 | + return "${text.result.autoCommitOff}"; | ||
1291 | + } else if (isBuiltIn(sql, "@cancel")) { | ||
1292 | + stat = session.executingStatement; | ||
1293 | + if (stat != null) { | ||
1294 | + stat.cancel(); | ||
1295 | + buff.append("${text.result.statementWasCanceled}"); | ||
1296 | + } else { | ||
1297 | + buff.append("${text.result.noRunningStatement}"); | ||
1298 | + } | ||
1299 | + return buff.toString(); | ||
1300 | + } else if (isBuiltIn(sql, "@edit")) { | ||
1301 | + edit = true; | ||
1302 | + sql = sql.substring("@edit".length()).trim(); | ||
1303 | + session.put("resultSetSQL", sql); | ||
1304 | + } | ||
1305 | + if (isBuiltIn(sql, "@list")) { | ||
1306 | + list = true; | ||
1307 | + sql = sql.substring("@list".length()).trim(); | ||
1308 | + } | ||
1309 | + if (isBuiltIn(sql, "@meta")) { | ||
1310 | + metadata = true; | ||
1311 | + sql = sql.substring("@meta".length()).trim(); | ||
1312 | + } | ||
1313 | + if (isBuiltIn(sql, "@generated")) { | ||
1314 | + generatedKeys = true; | ||
1315 | + sql = sql.substring("@generated".length()).trim(); | ||
1316 | + } else if (isBuiltIn(sql, "@history")) { | ||
1317 | + buff.append(getCommandHistoryString()); | ||
1318 | + return buff.toString(); | ||
1319 | + } else if (isBuiltIn(sql, "@loop")) { | ||
1320 | + sql = sql.substring("@loop".length()).trim(); | ||
1321 | + int idx = sql.indexOf(' '); | ||
1322 | + int count = Integer.decode(sql.substring(0, idx)); | ||
1323 | + sql = sql.substring(idx).trim(); | ||
1324 | + return executeLoop(conn, count, sql); | ||
1325 | + } else if (isBuiltIn(sql, "@maxrows")) { | ||
1326 | + int maxrows = (int) Double.parseDouble( | ||
1327 | + sql.substring("@maxrows".length()).trim()); | ||
1328 | + session.put("maxrows", "" + maxrows); | ||
1329 | + return "${text.result.maxrowsSet}"; | ||
1330 | + } else if (isBuiltIn(sql, "@parameter_meta")) { | ||
1331 | + sql = sql.substring("@parameter_meta".length()).trim(); | ||
1332 | + PreparedStatement prep = conn.prepareStatement(sql); | ||
1333 | + buff.append(getParameterResultSet(prep.getParameterMetaData())); | ||
1334 | + return buff.toString(); | ||
1335 | + } else if (isBuiltIn(sql, "@password_hash")) { | ||
1336 | + sql = sql.substring("@password_hash".length()).trim(); | ||
1337 | + String[] p = split(sql); | ||
1338 | + return StringUtils.convertBytesToHex( | ||
1339 | + SHA256.getKeyPasswordHash(p[0], p[1].toCharArray())); | ||
1340 | + } else if (isBuiltIn(sql, "@prof_start")) { | ||
1341 | + if (profiler != null) { | ||
1342 | + profiler.stopCollecting(); | ||
1343 | + } | ||
1344 | + profiler = new Profiler(); | ||
1345 | + profiler.startCollecting(); | ||
1346 | + return "Ok"; | ||
1347 | + } else if (isBuiltIn(sql, "@sleep")) { | ||
1348 | + String s = sql.substring("@sleep".length()).trim(); | ||
1349 | + int sleep = 1; | ||
1350 | + if (s.length() > 0) { | ||
1351 | + sleep = Integer.parseInt(s); | ||
1352 | + } | ||
1353 | + Thread.sleep(sleep * 1000); | ||
1354 | + return "Ok"; | ||
1355 | + } else if (isBuiltIn(sql, "@transaction_isolation")) { | ||
1356 | + String s = sql.substring("@transaction_isolation".length()).trim(); | ||
1357 | + if (s.length() > 0) { | ||
1358 | + int level = Integer.parseInt(s); | ||
1359 | + conn.setTransactionIsolation(level); | ||
1360 | + } | ||
1361 | + buff.append("Transaction Isolation: " + | ||
1362 | + conn.getTransactionIsolation() + "<br />"); | ||
1363 | + buff.append(Connection.TRANSACTION_READ_UNCOMMITTED + | ||
1364 | + ": read_uncommitted<br />"); | ||
1365 | + buff.append(Connection.TRANSACTION_READ_COMMITTED + | ||
1366 | + ": read_committed<br />"); | ||
1367 | + buff.append(Connection.TRANSACTION_REPEATABLE_READ + | ||
1368 | + ": repeatable_read<br />"); | ||
1369 | + buff.append(Connection.TRANSACTION_SERIALIZABLE + | ||
1370 | + ": serializable"); | ||
1371 | + } | ||
1372 | + if (sql.startsWith("@")) { | ||
1373 | + rs = getMetaResultSet(conn, sql); | ||
1374 | + if (rs == null) { | ||
1375 | + buff.append("?: " + sql); | ||
1376 | + return buff.toString(); | ||
1377 | + } | ||
1378 | + } else { | ||
1379 | + int maxrows = getMaxrows(); | ||
1380 | + stat.setMaxRows(maxrows); | ||
1381 | + session.executingStatement = stat; | ||
1382 | + boolean isResultSet = stat.execute(sql); | ||
1383 | + session.addCommand(sql); | ||
1384 | + if (generatedKeys) { | ||
1385 | + rs = null; | ||
1386 | + rs = stat.getGeneratedKeys(); | ||
1387 | + } else { | ||
1388 | + if (!isResultSet) { | ||
1389 | + buff.append("${text.result.updateCount}: " + stat.getUpdateCount()); | ||
1390 | + time = System.currentTimeMillis() - time; | ||
1391 | + buff.append("<br />(").append(time).append(" ms)"); | ||
1392 | + stat.close(); | ||
1393 | + return buff.toString(); | ||
1394 | + } | ||
1395 | + rs = stat.getResultSet(); | ||
1396 | + } | ||
1397 | + } | ||
1398 | + time = System.currentTimeMillis() - time; | ||
1399 | + buff.append(getResultSet(sql, rs, metadata, list, edit, time, allowEdit)); | ||
1400 | + // SQLWarning warning = stat.getWarnings(); | ||
1401 | + // if (warning != null) { | ||
1402 | + // buff.append("<br />Warning:<br />"). | ||
1403 | + // append(getStackTrace(id, warning)); | ||
1404 | + // } | ||
1405 | + if (!edit) { | ||
1406 | + stat.close(); | ||
1407 | + } | ||
1408 | + return buff.toString(); | ||
1409 | + } catch (Throwable e) { | ||
1410 | + // throwable: including OutOfMemoryError and so on | ||
1411 | + return getStackTrace(id, e, session.getContents().isH2()); | ||
1412 | + } finally { | ||
1413 | + session.executingStatement = null; | ||
1414 | + } | ||
1415 | + } | ||
1416 | + | ||
1417 | + private static boolean isBuiltIn(String sql, String builtIn) { | ||
1418 | + return StringUtils.startsWithIgnoreCase(sql, builtIn); | ||
1419 | + } | ||
1420 | + | ||
1421 | + private String executeLoop(Connection conn, int count, String sql) | ||
1422 | + throws SQLException { | ||
1423 | + ArrayList<Integer> params = New.arrayList(); | ||
1424 | + int idx = 0; | ||
1425 | + while (!stop) { | ||
1426 | + idx = sql.indexOf('?', idx); | ||
1427 | + if (idx < 0) { | ||
1428 | + break; | ||
1429 | + } | ||
1430 | + if (isBuiltIn(sql.substring(idx), "?/*rnd*/")) { | ||
1431 | + params.add(1); | ||
1432 | + sql = sql.substring(0, idx) + "?" + sql.substring(idx + "/*rnd*/".length() + 1); | ||
1433 | + } else { | ||
1434 | + params.add(0); | ||
1435 | + } | ||
1436 | + idx++; | ||
1437 | + } | ||
1438 | + boolean prepared; | ||
1439 | + Random random = new Random(1); | ||
1440 | + long time = System.currentTimeMillis(); | ||
1441 | + if (isBuiltIn(sql, "@statement")) { | ||
1442 | + sql = sql.substring("@statement".length()).trim(); | ||
1443 | + prepared = false; | ||
1444 | + Statement stat = conn.createStatement(); | ||
1445 | + for (int i = 0; !stop && i < count; i++) { | ||
1446 | + String s = sql; | ||
1447 | + for (Integer type : params) { | ||
1448 | + idx = s.indexOf('?'); | ||
1449 | + if (type.intValue() == 1) { | ||
1450 | + s = s.substring(0, idx) + random.nextInt(count) + s.substring(idx + 1); | ||
1451 | + } else { | ||
1452 | + s = s.substring(0, idx) + i + s.substring(idx + 1); | ||
1453 | + } | ||
1454 | + } | ||
1455 | + if (stat.execute(s)) { | ||
1456 | + ResultSet rs = stat.getResultSet(); | ||
1457 | + while (!stop && rs.next()) { | ||
1458 | + // maybe get the data as well | ||
1459 | + } | ||
1460 | + rs.close(); | ||
1461 | + } | ||
1462 | + } | ||
1463 | + } else { | ||
1464 | + prepared = true; | ||
1465 | + PreparedStatement prep = conn.prepareStatement(sql); | ||
1466 | + for (int i = 0; !stop && i < count; i++) { | ||
1467 | + for (int j = 0; j < params.size(); j++) { | ||
1468 | + Integer type = params.get(j); | ||
1469 | + if (type.intValue() == 1) { | ||
1470 | + prep.setInt(j + 1, random.nextInt(count)); | ||
1471 | + } else { | ||
1472 | + prep.setInt(j + 1, i); | ||
1473 | + } | ||
1474 | + } | ||
1475 | + if (session.getContents().isSQLite()) { | ||
1476 | + // SQLite currently throws an exception on prep.execute() | ||
1477 | + prep.executeUpdate(); | ||
1478 | + } else { | ||
1479 | + if (prep.execute()) { | ||
1480 | + ResultSet rs = prep.getResultSet(); | ||
1481 | + while (!stop && rs.next()) { | ||
1482 | + // maybe get the data as well | ||
1483 | + } | ||
1484 | + rs.close(); | ||
1485 | + } | ||
1486 | + } | ||
1487 | + } | ||
1488 | + } | ||
1489 | + time = System.currentTimeMillis() - time; | ||
1490 | + StatementBuilder buff = new StatementBuilder(); | ||
1491 | + buff.append(time).append(" ms: ").append(count).append(" * "); | ||
1492 | + if (prepared) { | ||
1493 | + buff.append("(Prepared) "); | ||
1494 | + } else { | ||
1495 | + buff.append("(Statement) "); | ||
1496 | + } | ||
1497 | + buff.append('('); | ||
1498 | + for (int p : params) { | ||
1499 | + buff.appendExceptFirst(", "); | ||
1500 | + buff.append(p == 0 ? "i" : "rnd"); | ||
1501 | + } | ||
1502 | + return buff.append(") ").append(sql).toString(); | ||
1503 | + } | ||
1504 | + | ||
1505 | + private String getCommandHistoryString() { | ||
1506 | + StringBuilder buff = new StringBuilder(); | ||
1507 | + ArrayList<String> history = session.getCommandHistory(); | ||
1508 | + buff.append("<table cellspacing=0 cellpadding=0>" + | ||
1509 | + "<tr><th></th><th>Command</th></tr>"); | ||
1510 | + for (int i = history.size() - 1; i >= 0; i--) { | ||
1511 | + String sql = history.get(i); | ||
1512 | + buff.append("<tr><td><a href=\"getHistory.do?id="). | ||
1513 | + append(i). | ||
1514 | + append("&jsessionid=${sessionId}\" target=\"h2query\" >"). | ||
1515 | + append("<img width=16 height=16 src=\"ico_write.gif\" " + | ||
1516 | + "onmouseover = \"this.className ='icon_hover'\" "). | ||
1517 | + append("onmouseout = \"this.className ='icon'\" " + | ||
1518 | + "class=\"icon\" alt=\"${text.resultEdit.edit}\" "). | ||
1519 | + append("title=\"${text.resultEdit.edit}\" border=\"1\"/></a>"). | ||
1520 | + append("</td><td>"). | ||
1521 | + append(PageParser.escapeHtml(sql)). | ||
1522 | + append("</td></tr>"); | ||
1523 | + } | ||
1524 | + buff.append("</table>"); | ||
1525 | + return buff.toString(); | ||
1526 | + } | ||
1527 | + | ||
1528 | + private static String getParameterResultSet(ParameterMetaData meta) | ||
1529 | + throws SQLException { | ||
1530 | + StringBuilder buff = new StringBuilder(); | ||
1531 | + if (meta == null) { | ||
1532 | + return "No parameter meta data"; | ||
1533 | + } | ||
1534 | + buff.append("<table cellspacing=0 cellpadding=0>"). | ||
1535 | + append("<tr><th>className</th><th>mode</th><th>type</th>"). | ||
1536 | + append("<th>typeName</th><th>precision</th><th>scale</th></tr>"); | ||
1537 | + for (int i = 0; i < meta.getParameterCount(); i++) { | ||
1538 | + buff.append("</tr><td>"). | ||
1539 | + append(meta.getParameterClassName(i + 1)). | ||
1540 | + append("</td><td>"). | ||
1541 | + append(meta.getParameterMode(i + 1)). | ||
1542 | + append("</td><td>"). | ||
1543 | + append(meta.getParameterType(i + 1)). | ||
1544 | + append("</td><td>"). | ||
1545 | + append(meta.getParameterTypeName(i + 1)). | ||
1546 | + append("</td><td>"). | ||
1547 | + append(meta.getPrecision(i + 1)). | ||
1548 | + append("</td><td>"). | ||
1549 | + append(meta.getScale(i + 1)). | ||
1550 | + append("</td></tr>"); | ||
1551 | + } | ||
1552 | + buff.append("</table>"); | ||
1553 | + return buff.toString(); | ||
1554 | + } | ||
1555 | + | ||
1556 | + private String getResultSet(String sql, ResultSet rs, boolean metadata, | ||
1557 | + boolean list, boolean edit, long time, boolean allowEdit) | ||
1558 | + throws SQLException { | ||
1559 | + int maxrows = getMaxrows(); | ||
1560 | + time = System.currentTimeMillis() - time; | ||
1561 | + StringBuilder buff = new StringBuilder(); | ||
1562 | + if (edit) { | ||
1563 | + buff.append("<form id=\"editing\" name=\"editing\" method=\"post\" " + | ||
1564 | + "action=\"editResult.do?jsessionid=${sessionId}\" " + | ||
1565 | + "id=\"mainForm\" target=\"h2result\">" + | ||
1566 | + "<input type=\"hidden\" name=\"op\" value=\"1\" />" + | ||
1567 | + "<input type=\"hidden\" name=\"row\" value=\"\" />" + | ||
1568 | + "<table cellspacing=0 cellpadding=0 id=\"editTable\">"); | ||
1569 | + } else { | ||
1570 | + buff.append("<table cellspacing=0 cellpadding=0>"); | ||
1571 | + } | ||
1572 | + if (metadata) { | ||
1573 | + SimpleResultSet r = new SimpleResultSet(); | ||
1574 | + r.addColumn("#", Types.INTEGER, 0, 0); | ||
1575 | + r.addColumn("label", Types.VARCHAR, 0, 0); | ||
1576 | + r.addColumn("catalog", Types.VARCHAR, 0, 0); | ||
1577 | + r.addColumn("schema", Types.VARCHAR, 0, 0); | ||
1578 | + r.addColumn("table", Types.VARCHAR, 0, 0); | ||
1579 | + r.addColumn("column", Types.VARCHAR, 0, 0); | ||
1580 | + r.addColumn("type", Types.INTEGER, 0, 0); | ||
1581 | + r.addColumn("typeName", Types.VARCHAR, 0, 0); | ||
1582 | + r.addColumn("class", Types.VARCHAR, 0, 0); | ||
1583 | + r.addColumn("precision", Types.INTEGER, 0, 0); | ||
1584 | + r.addColumn("scale", Types.INTEGER, 0, 0); | ||
1585 | + r.addColumn("displaySize", Types.INTEGER, 0, 0); | ||
1586 | + r.addColumn("autoIncrement", Types.BOOLEAN, 0, 0); | ||
1587 | + r.addColumn("caseSensitive", Types.BOOLEAN, 0, 0); | ||
1588 | + r.addColumn("currency", Types.BOOLEAN, 0, 0); | ||
1589 | + r.addColumn("nullable", Types.INTEGER, 0, 0); | ||
1590 | + r.addColumn("readOnly", Types.BOOLEAN, 0, 0); | ||
1591 | + r.addColumn("searchable", Types.BOOLEAN, 0, 0); | ||
1592 | + r.addColumn("signed", Types.BOOLEAN, 0, 0); | ||
1593 | + r.addColumn("writable", Types.BOOLEAN, 0, 0); | ||
1594 | + r.addColumn("definitelyWritable", Types.BOOLEAN, 0, 0); | ||
1595 | + ResultSetMetaData m = rs.getMetaData(); | ||
1596 | + for (int i = 1; i <= m.getColumnCount(); i++) { | ||
1597 | + r.addRow(i, | ||
1598 | + m.getColumnLabel(i), | ||
1599 | + m.getCatalogName(i), | ||
1600 | + m.getSchemaName(i), | ||
1601 | + m.getTableName(i), | ||
1602 | + m.getColumnName(i), | ||
1603 | + m.getColumnType(i), | ||
1604 | + m.getColumnTypeName(i), | ||
1605 | + m.getColumnClassName(i), | ||
1606 | + m.getPrecision(i), | ||
1607 | + m.getScale(i), | ||
1608 | + m.getColumnDisplaySize(i), | ||
1609 | + m.isAutoIncrement(i), | ||
1610 | + m.isCaseSensitive(i), | ||
1611 | + m.isCurrency(i), | ||
1612 | + m.isNullable(i), | ||
1613 | + m.isReadOnly(i), | ||
1614 | + m.isSearchable(i), | ||
1615 | + m.isSigned(i), | ||
1616 | + m.isWritable(i), | ||
1617 | + m.isDefinitelyWritable(i)); | ||
1618 | + } | ||
1619 | + rs = r; | ||
1620 | + } | ||
1621 | + ResultSetMetaData meta = rs.getMetaData(); | ||
1622 | + int columns = meta.getColumnCount(); | ||
1623 | + int rows = 0; | ||
1624 | + if (list) { | ||
1625 | + buff.append("<tr><th>Column</th><th>Data</th></tr><tr>"); | ||
1626 | + while (rs.next()) { | ||
1627 | + if (maxrows > 0 && rows >= maxrows) { | ||
1628 | + break; | ||
1629 | + } | ||
1630 | + rows++; | ||
1631 | + buff.append("<tr><td>Row #</td><td>"). | ||
1632 | + append(rows).append("</tr>"); | ||
1633 | + for (int i = 0; i < columns; i++) { | ||
1634 | + buff.append("<tr><td>"). | ||
1635 | + append(PageParser.escapeHtml(meta.getColumnLabel(i + 1))). | ||
1636 | + append("</td><td>"). | ||
1637 | + append(escapeData(rs, i + 1)). | ||
1638 | + append("</td></tr>"); | ||
1639 | + } | ||
1640 | + } | ||
1641 | + } else { | ||
1642 | + buff.append("<tr>"); | ||
1643 | + if (edit) { | ||
1644 | + buff.append("<th>${text.resultEdit.action}</th>"); | ||
1645 | + } | ||
1646 | + for (int i = 0; i < columns; i++) { | ||
1647 | + buff.append("<th>"). | ||
1648 | + append(PageParser.escapeHtml(meta.getColumnLabel(i + 1))). | ||
1649 | + append("</th>"); | ||
1650 | + } | ||
1651 | + buff.append("</tr>"); | ||
1652 | + while (rs.next()) { | ||
1653 | + if (maxrows > 0 && rows >= maxrows) { | ||
1654 | + break; | ||
1655 | + } | ||
1656 | + rows++; | ||
1657 | + buff.append("<tr>"); | ||
1658 | + if (edit) { | ||
1659 | + buff.append("<td>"). | ||
1660 | + append("<img onclick=\"javascript:editRow("). | ||
1661 | + append(rs.getRow()). | ||
1662 | + append(",'${sessionId}', '${text.resultEdit.save}', " + | ||
1663 | + "'${text.resultEdit.cancel}'"). | ||
1664 | + append(")\" width=16 height=16 src=\"ico_write.gif\" " + | ||
1665 | + "onmouseover = \"this.className ='icon_hover'\" " + | ||
1666 | + "onmouseout = \"this.className ='icon'\" " + | ||
1667 | + "class=\"icon\" alt=\"${text.resultEdit.edit}\" " + | ||
1668 | + "title=\"${text.resultEdit.edit}\" border=\"1\"/>"). | ||
1669 | + append("<a href=\"editResult.do?op=2&row="). | ||
1670 | + append(rs.getRow()). | ||
1671 | + append("&jsessionid=${sessionId}\" target=\"h2result\" >" + | ||
1672 | + "<img width=16 height=16 src=\"ico_remove.gif\" " + | ||
1673 | + "onmouseover = \"this.className ='icon_hover'\" " + | ||
1674 | + "onmouseout = \"this.className ='icon'\" " + | ||
1675 | + "class=\"icon\" alt=\"${text.resultEdit.delete}\" " + | ||
1676 | + "title=\"${text.resultEdit.delete}\" border=\"1\" /></a>"). | ||
1677 | + append("</td>"); | ||
1678 | + } | ||
1679 | + for (int i = 0; i < columns; i++) { | ||
1680 | + buff.append("<td>"). | ||
1681 | + append(escapeData(rs, i + 1)). | ||
1682 | + append("</td>"); | ||
1683 | + } | ||
1684 | + buff.append("</tr>"); | ||
1685 | + } | ||
1686 | + } | ||
1687 | + boolean isUpdatable = false; | ||
1688 | + try { | ||
1689 | + if (!session.getContents().isDB2()) { | ||
1690 | + isUpdatable = rs.getConcurrency() == ResultSet.CONCUR_UPDATABLE | ||
1691 | + && rs.getType() != ResultSet.TYPE_FORWARD_ONLY; | ||
1692 | + } | ||
1693 | + } catch (NullPointerException e) { | ||
1694 | + // ignore | ||
1695 | + // workaround for a JDBC-ODBC bridge problem | ||
1696 | + } | ||
1697 | + if (edit) { | ||
1698 | + ResultSet old = session.result; | ||
1699 | + if (old != null) { | ||
1700 | + old.close(); | ||
1701 | + } | ||
1702 | + session.result = rs; | ||
1703 | + } else { | ||
1704 | + rs.close(); | ||
1705 | + } | ||
1706 | + if (edit) { | ||
1707 | + buff.append("<tr><td>"). | ||
1708 | + append("<img onclick=\"javascript:editRow(-1, " + | ||
1709 | + "'${sessionId}', '${text.resultEdit.save}', '${text.resultEdit.cancel}'"). | ||
1710 | + append(")\" width=16 height=16 src=\"ico_add.gif\" " + | ||
1711 | + "onmouseover = \"this.className ='icon_hover'\" " + | ||
1712 | + "onmouseout = \"this.className ='icon'\" " + | ||
1713 | + "class=\"icon\" alt=\"${text.resultEdit.add}\" " + | ||
1714 | + "title=\"${text.resultEdit.add}\" border=\"1\"/>"). | ||
1715 | + append("</td>"); | ||
1716 | + for (int i = 0; i < columns; i++) { | ||
1717 | + buff.append("<td></td>"); | ||
1718 | + } | ||
1719 | + buff.append("</tr>"); | ||
1720 | + } | ||
1721 | + buff.append("</table>"); | ||
1722 | + if (edit) { | ||
1723 | + buff.append("</form>"); | ||
1724 | + } | ||
1725 | + if (rows == 0) { | ||
1726 | + buff.append("(${text.result.noRows}"); | ||
1727 | + } else if (rows == 1) { | ||
1728 | + buff.append("(${text.result.1row}"); | ||
1729 | + } else { | ||
1730 | + buff.append('(').append(rows).append(" ${text.result.rows}"); | ||
1731 | + } | ||
1732 | + buff.append(", "); | ||
1733 | + time = System.currentTimeMillis() - time; | ||
1734 | + buff.append(time).append(" ms)"); | ||
1735 | + if (!edit && isUpdatable && allowEdit) { | ||
1736 | + buff.append("<br /><br />" + | ||
1737 | + "<form name=\"editResult\" method=\"post\" " + | ||
1738 | + "action=\"query.do?jsessionid=${sessionId}\" target=\"h2result\">" + | ||
1739 | + "<input type=\"submit\" class=\"button\" " + | ||
1740 | + "value=\"${text.resultEdit.editResult}\" />" + | ||
1741 | + "<input type=\"hidden\" name=\"sql\" value=\"@edit "). | ||
1742 | + append(PageParser.escapeHtmlData(sql)). | ||
1743 | + append("\" /></form>"); | ||
1744 | + } | ||
1745 | + return buff.toString(); | ||
1746 | + } | ||
1747 | + | ||
1748 | + /** | ||
1749 | + * Save the current connection settings to the properties file. | ||
1750 | + * | ||
1751 | + * @return the file to open afterwards | ||
1752 | + */ | ||
1753 | + private String settingSave() { | ||
1754 | + ConnectionInfo info = new ConnectionInfo(); | ||
1755 | + info.name = attributes.getProperty("name", ""); | ||
1756 | + info.driver = attributes.getProperty("driver", ""); | ||
1757 | + info.url = attributes.getProperty("url", ""); | ||
1758 | + info.user = attributes.getProperty("user", ""); | ||
1759 | + server.updateSetting(info); | ||
1760 | + attributes.put("setting", info.name); | ||
1761 | + server.saveProperties(null); | ||
1762 | + return "index.do"; | ||
1763 | + } | ||
1764 | + | ||
1765 | + private static String escapeData(ResultSet rs, int columnIndex) | ||
1766 | + throws SQLException { | ||
1767 | + String d = rs.getString(columnIndex); | ||
1768 | + if (d == null) { | ||
1769 | + return "<i>null</i>"; | ||
1770 | + } else if (d.length() > 100000) { | ||
1771 | + String s; | ||
1772 | + if (isBinary(rs.getMetaData().getColumnType(columnIndex))) { | ||
1773 | + s = PageParser.escapeHtml(d.substring(0, 6)) + | ||
1774 | + "... (" + (d.length() / 2) + " ${text.result.bytes})"; | ||
1775 | + } else { | ||
1776 | + s = PageParser.escapeHtml(d.substring(0, 100)) + | ||
1777 | + "... (" + d.length() + " ${text.result.characters})"; | ||
1778 | + } | ||
1779 | + return "<div style='display: none'>=+</div>" + s; | ||
1780 | + } else if (d.equals("null") || d.startsWith("= ") || d.startsWith("=+")) { | ||
1781 | + return "<div style='display: none'>= </div>" + PageParser.escapeHtml(d); | ||
1782 | + } else if (d.equals("")) { | ||
1783 | + // PageParser.escapeHtml replaces "" with a non-breaking space | ||
1784 | + return ""; | ||
1785 | + } | ||
1786 | + return PageParser.escapeHtml(d); | ||
1787 | + } | ||
1788 | + | ||
1789 | + private static boolean isBinary(int sqlType) { | ||
1790 | + switch (sqlType) { | ||
1791 | + case Types.BINARY: | ||
1792 | + case Types.BLOB: | ||
1793 | + case Types.JAVA_OBJECT: | ||
1794 | + case Types.LONGVARBINARY: | ||
1795 | + case Types.OTHER: | ||
1796 | + case Types.VARBINARY: | ||
1797 | + return true; | ||
1798 | + } | ||
1799 | + return false; | ||
1800 | + } | ||
1801 | + | ||
1802 | + private void unescapeData(String x, ResultSet rs, int columnIndex) | ||
1803 | + throws SQLException { | ||
1804 | + if (x.equals("null")) { | ||
1805 | + rs.updateNull(columnIndex); | ||
1806 | + return; | ||
1807 | + } else if (x.startsWith("=+")) { | ||
1808 | + // don't update | ||
1809 | + return; | ||
1810 | + } else if (x.equals("=*")) { | ||
1811 | + // set an appropriate default value | ||
1812 | + int type = rs.getMetaData().getColumnType(columnIndex); | ||
1813 | + switch (type) { | ||
1814 | + case Types.TIME: | ||
1815 | + rs.updateString(columnIndex, "12:00:00"); | ||
1816 | + break; | ||
1817 | + case Types.TIMESTAMP: | ||
1818 | + case Types.DATE: | ||
1819 | + rs.updateString(columnIndex, "2001-01-01"); | ||
1820 | + break; | ||
1821 | + default: | ||
1822 | + rs.updateString(columnIndex, "1"); | ||
1823 | + break; | ||
1824 | + } | ||
1825 | + return; | ||
1826 | + } else if (x.startsWith("= ")) { | ||
1827 | + x = x.substring(2); | ||
1828 | + } | ||
1829 | + ResultSetMetaData meta = rs.getMetaData(); | ||
1830 | + int type = meta.getColumnType(columnIndex); | ||
1831 | + if (session.getContents().isH2()) { | ||
1832 | + rs.updateString(columnIndex, x); | ||
1833 | + return; | ||
1834 | + } | ||
1835 | + switch (type) { | ||
1836 | + case Types.BIGINT: | ||
1837 | + rs.updateLong(columnIndex, Long.decode(x)); | ||
1838 | + break; | ||
1839 | + case Types.DECIMAL: | ||
1840 | + rs.updateBigDecimal(columnIndex, new BigDecimal(x)); | ||
1841 | + break; | ||
1842 | + case Types.DOUBLE: | ||
1843 | + case Types.FLOAT: | ||
1844 | + rs.updateDouble(columnIndex, Double.parseDouble(x)); | ||
1845 | + break; | ||
1846 | + case Types.REAL: | ||
1847 | + rs.updateFloat(columnIndex, Float.parseFloat(x)); | ||
1848 | + break; | ||
1849 | + case Types.INTEGER: | ||
1850 | + rs.updateInt(columnIndex, Integer.decode(x)); | ||
1851 | + break; | ||
1852 | + case Types.TINYINT: | ||
1853 | + rs.updateShort(columnIndex, Short.decode(x)); | ||
1854 | + break; | ||
1855 | + default: | ||
1856 | + rs.updateString(columnIndex, x); | ||
1857 | + } | ||
1858 | + } | ||
1859 | + | ||
1860 | + private String settingRemove() { | ||
1861 | + String setting = attributes.getProperty("name", ""); | ||
1862 | + server.removeSetting(setting); | ||
1863 | + ArrayList<ConnectionInfo> settings = server.getSettings(); | ||
1864 | + if (settings.size() > 0) { | ||
1865 | + attributes.put("setting", settings.get(0)); | ||
1866 | + } | ||
1867 | + server.saveProperties(null); | ||
1868 | + return "index.do"; | ||
1869 | + } | ||
1870 | + | ||
1871 | + /** | ||
1872 | + * Get the current mime type. | ||
1873 | + * | ||
1874 | + * @return the mime type | ||
1875 | + */ | ||
1876 | + String getMimeType() { | ||
1877 | + return mimeType; | ||
1878 | + } | ||
1879 | + | ||
1880 | + boolean getCache() { | ||
1881 | + return cache; | ||
1882 | + } | ||
1883 | + | ||
1884 | + WebSession getSession() { | ||
1885 | + return session; | ||
1886 | + } | ||
1887 | + | ||
1888 | + private void trace(String s) { | ||
1889 | + server.trace(s); | ||
1890 | + } | ||
1891 | + | ||
1892 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
src/main/java/org/legrog/util/WebServer.java
0 → 100644
1 | +package org.legrog.util; | ||
2 | +// Code copied from org.h2.server.web.WebServer | ||
3 | +/* | ||
4 | + * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, | ||
5 | + * and the EPL 1.0 (http://h2database.com/html/license.html). | ||
6 | + * Initial Developer: H2 Group | ||
7 | + */ | ||
8 | +//package org.h2.server.web; | ||
9 | + | ||
10 | + import java.io.File; | ||
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.HashMap; | ||
22 | + import java.util.HashSet; | ||
23 | + import java.util.Locale; | ||
24 | + import java.util.Map; | ||
25 | + import java.util.Map.Entry; | ||
26 | + import java.util.Properties; | ||
27 | + import java.util.Set; | ||
28 | + import java.util.TimeZone; | ||
29 | + | ||
30 | + import org.h2.engine.Constants; | ||
31 | + import org.h2.engine.SysProperties; | ||
32 | + import org.h2.message.DbException; | ||
33 | + import org.h2.server.Service; | ||
34 | + import org.h2.server.ShutdownHandler; | ||
35 | + import org.h2.store.fs.FileUtils; | ||
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[][] LANGUAGES = { | ||
52 | + { "cs", "\u010ce\u0161tina" }, | ||
53 | + { "de", "Deutsch" }, | ||
54 | + { "en", "English" }, | ||
55 | + { "es", "Espa\u00f1ol" }, | ||
56 | + { "fr", "Fran\u00e7ais" }, | ||
57 | + { "hu", "Magyar"}, | ||
58 | + { "ko", "\ud55c\uad6d\uc5b4"}, | ||
59 | + { "in", "Indonesia"}, | ||
60 | + { "it", "Italiano"}, | ||
61 | + { "ja", "\u65e5\u672c\u8a9e"}, | ||
62 | + { "nl", "Nederlands"}, | ||
63 | + { "pl", "Polski"}, | ||
64 | + { "pt_BR", "Portugu\u00eas (Brasil)"}, | ||
65 | + { "pt_PT", "Portugu\u00eas (Europeu)"}, | ||
66 | + { "ru", "\u0440\u0443\u0441\u0441\u043a\u0438\u0439"}, | ||
67 | + { "sk", "Slovensky"}, | ||
68 | + { "tr", "T\u00fcrk\u00e7e"}, | ||
69 | + { "uk", "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430"}, | ||
70 | + { "zh_CN", "\u4e2d\u6587 (\u7b80\u4f53)"}, | ||
71 | + { "zh_TW", "\u4e2d\u6587 (\u7e41\u9ad4)"}, | ||
72 | + }; | ||
73 | + | ||
74 | + private static final String COMMAND_HISTORY = "commandHistory"; | ||
75 | + | ||
76 | + private static final String DEFAULT_LANGUAGE = "en"; | ||
77 | + | ||
78 | + private static final String[] GENERIC = { | ||
79 | + "Generic JNDI Data Source|javax.naming.InitialContext|" + | ||
80 | + "java:comp/env/jdbc/Test|sa", | ||
81 | + "Generic Firebird Server|org.firebirdsql.jdbc.FBDriver|" + | ||
82 | + "jdbc:firebirdsql:localhost:c:/temp/firebird/test|sysdba", | ||
83 | + "Generic SQLite|org.sqlite.JDBC|" + | ||
84 | + "jdbc:sqlite:test|sa", | ||
85 | + "Generic DB2|com.ibm.db2.jcc.DB2Driver|" + | ||
86 | + "jdbc:db2://localhost/test|" , | ||
87 | + "Generic Oracle|oracle.jdbc.driver.OracleDriver|" + | ||
88 | + "jdbc:oracle:thin:@localhost:1521:XE|sa" , | ||
89 | + "Generic MS SQL Server 2000|com.microsoft.jdbc.sqlserver.SQLServerDriver|" + | ||
90 | + "jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=sqlexpress|sa", | ||
91 | + "Generic MS SQL Server 2005|com.microsoft.sqlserver.jdbc.SQLServerDriver|" + | ||
92 | + "jdbc:sqlserver://localhost;DatabaseName=test|sa", | ||
93 | + "Generic PostgreSQL|org.postgresql.Driver|" + | ||
94 | + "jdbc:postgresql:test|" , | ||
95 | + "Generic MySQL|com.mysql.jdbc.Driver|" + | ||
96 | + "jdbc:mysql://localhost:3306/test|" , | ||
97 | + "Generic HSQLDB|org.hsqldb.jdbcDriver|" + | ||
98 | + "jdbc:hsqldb:test;hsqldb.default_table_type=cached|sa" , | ||
99 | + "Generic Derby (Server)|org.apache.derby.jdbc.ClientDriver|" + | ||
100 | + "jdbc:derby://localhost:1527/test;create=true|sa", | ||
101 | + "Generic Derby (Embedded)|org.apache.derby.jdbc.EmbeddedDriver|" + | ||
102 | + "jdbc:derby:test;create=true|sa", | ||
103 | + "Generic H2 (Server)|org.h2.Driver|" + | ||
104 | + "jdbc:h2:tcp://localhost/~/test|sa", | ||
105 | + // this will be listed on top for new installations | ||
106 | + "Generic H2 (Embedded)|org.h2.Driver|" + | ||
107 | + "jdbc:h2:~/test|sa", | ||
108 | + }; | ||
109 | + | ||
110 | + private static int ticker; | ||
111 | + | ||
112 | + /** | ||
113 | + * The session timeout (the default is 30 minutes). | ||
114 | + */ | ||
115 | + private static final long SESSION_TIMEOUT = SysProperties.CONSOLE_TIMEOUT; | ||
116 | + | ||
117 | +// public static void main(String... args) throws IOException { | ||
118 | +// String s = IOUtils.readStringAndClose(new java.io.FileReader( | ||
119 | +// // "src/main/org/h2/server/web/res/_text_cs.prop"), -1); | ||
120 | +// "src/main/org/h2/res/_messages_cs.prop"), -1); | ||
121 | +// System.out.println(StringUtils.javaEncode("...")); | ||
122 | +// String[] list = Locale.getISOLanguages(); | ||
123 | +// for (int i = 0; i < list.length; i++) { | ||
124 | +// System.out.print(list[i] + " "); | ||
125 | +// } | ||
126 | +// System.out.println(); | ||
127 | +// String l = "de"; | ||
128 | +// String lang = new java.util.Locale(l). | ||
129 | +// getDisplayLanguage(new java.util.Locale(l)); | ||
130 | +// System.out.println(new java.util.Locale(l).getDisplayLanguage()); | ||
131 | +// System.out.println(lang); | ||
132 | +// java.util.Locale.CHINESE.getDisplayLanguage(java.util.Locale.CHINESE); | ||
133 | +// for (int i = 0; i < lang.length(); i++) { | ||
134 | +// System.out.println(Integer.toHexString(lang.charAt(i)) + " "); | ||
135 | +// } | ||
136 | +// } | ||
137 | + | ||
138 | + // private URLClassLoader urlClassLoader; | ||
139 | + private int port; | ||
140 | + private boolean allowOthers; | ||
141 | + private boolean isDaemon; | ||
142 | + private final Set<WebThread> running = | ||
143 | + Collections.synchronizedSet(new HashSet<WebThread>()); | ||
144 | + private boolean ssl; | ||
145 | + private final HashMap<String, ConnectionInfo> connInfoMap = New.hashMap(); | ||
146 | + | ||
147 | + private long lastTimeoutCheck; | ||
148 | + private final HashMap<String, WebSession> sessions = New.hashMap(); | ||
149 | + private final HashSet<String> languages = New.hashSet(); | ||
150 | + private String startDateTime; | ||
151 | + private ServerSocket serverSocket; | ||
152 | + private String url; | ||
153 | + private ShutdownHandler shutdownHandler; | ||
154 | + private Thread listenerThread; | ||
155 | + private boolean ifExists; | ||
156 | + private boolean trace; | ||
157 | + private TranslateThread translateThread; | ||
158 | + private boolean allowChunked = true; | ||
159 | + private String serverPropertiesDir = Constants.SERVER_PROPERTIES_DIR; | ||
160 | + // null means the history is not allowed to be stored | ||
161 | + private String commandHistoryString; | ||
162 | + | ||
163 | + /** | ||
164 | + * Read the given file from the file system or from the resources. | ||
165 | + * | ||
166 | + * @param file the file name | ||
167 | + * @return the data | ||
168 | + */ | ||
169 | + byte[] getFile(String file) throws IOException { | ||
170 | + trace("getFile <" + file + ">"); | ||
171 | + byte[] data = Utils.getResource("/org/h2/server/web/res/" + file); | ||
172 | + if (data == null) { | ||
173 | + trace(" null"); | ||
174 | + } else { | ||
175 | + trace(" size=" + data.length); | ||
176 | + } | ||
177 | + return data; | ||
178 | + } | ||
179 | + | ||
180 | + /** | ||
181 | + * Check if this is a simple name (only contains '.', '-', '_', letters, or | ||
182 | + * digits). | ||
183 | + * | ||
184 | + * @param s the string | ||
185 | + * @return true if it's a simple name | ||
186 | + */ | ||
187 | + static boolean isSimpleName(String s) { | ||
188 | + for (char c : s.toCharArray()) { | ||
189 | + if (c != '.' && c != '_' && c != '-' && !Character.isLetterOrDigit(c)) { | ||
190 | + return false; | ||
191 | + } | ||
192 | + } | ||
193 | + return true; | ||
194 | + } | ||
195 | + | ||
196 | + /** | ||
197 | + * Remove this web thread from the set of running threads. | ||
198 | + * | ||
199 | + * @param t the thread to remove | ||
200 | + */ | ||
201 | + synchronized void remove(WebThread t) { | ||
202 | + running.remove(t); | ||
203 | + } | ||
204 | + | ||
205 | + private static String generateSessionId() { | ||
206 | + byte[] buff = MathUtils.secureRandomBytes(16); | ||
207 | + return StringUtils.convertBytesToHex(buff); | ||
208 | + } | ||
209 | + | ||
210 | + /** | ||
211 | + * Get the web session object for the given session id. | ||
212 | + * | ||
213 | + * @param sessionId the session id | ||
214 | + * @return the web session or null | ||
215 | + */ | ||
216 | + WebSession getSession(String sessionId) { | ||
217 | + long now = System.currentTimeMillis(); | ||
218 | + if (lastTimeoutCheck + SESSION_TIMEOUT < now) { | ||
219 | + for (String id : New.arrayList(sessions.keySet())) { | ||
220 | + WebSession session = sessions.get(id); | ||
221 | + if (session.lastAccess + SESSION_TIMEOUT < now) { | ||
222 | + trace("timeout for " + id); | ||
223 | + sessions.remove(id); | ||
224 | + } | ||
225 | + } | ||
226 | + lastTimeoutCheck = now; | ||
227 | + } | ||
228 | + WebSession session = sessions.get(sessionId); | ||
229 | + if (session != null) { | ||
230 | + session.lastAccess = System.currentTimeMillis(); | ||
231 | + } | ||
232 | + return session; | ||
233 | + } | ||
234 | + | ||
235 | + /** | ||
236 | + * Create a new web session id and object. | ||
237 | + * | ||
238 | + * @param hostAddr the host address | ||
239 | + * @return the web session object | ||
240 | + */ | ||
241 | + WebSession createNewSession(String hostAddr) { | ||
242 | + String newId; | ||
243 | + do { | ||
244 | + newId = generateSessionId(); | ||
245 | + } while (sessions.get(newId) != null); | ||
246 | + WebSession session = new WebSession(this); | ||
247 | + session.lastAccess = System.currentTimeMillis(); | ||
248 | + session.put("sessionId", newId); | ||
249 | + session.put("ip", hostAddr); | ||
250 | + session.put("language", DEFAULT_LANGUAGE); | ||
251 | + session.put("frame-border", "0"); | ||
252 | + session.put("frameset-border", "4"); | ||
253 | + sessions.put(newId, session); | ||
254 | + // always read the english translation, | ||
255 | + // so that untranslated text appears at least in english | ||
256 | + readTranslations(session, DEFAULT_LANGUAGE); | ||
257 | + return getSession(newId); | ||
258 | + } | ||
259 | + | ||
260 | + String getStartDateTime() { | ||
261 | + if (startDateTime == null) { | ||
262 | + SimpleDateFormat format = new SimpleDateFormat( | ||
263 | + "EEE, d MMM yyyy HH:mm:ss z", new Locale("en", "")); | ||
264 | + format.setTimeZone(TimeZone.getTimeZone("GMT")); | ||
265 | + startDateTime = format.format(System.currentTimeMillis()); | ||
266 | + } | ||
267 | + return startDateTime; | ||
268 | + } | ||
269 | + | ||
270 | + @Override | ||
271 | + public void init(String... args) { | ||
272 | + // set the serverPropertiesDir, because it's used in loadProperties() | ||
273 | + for (int i = 0; args != null && i < args.length; i++) { | ||
274 | + if ("-properties".equals(args[i])) { | ||
275 | + serverPropertiesDir = args[++i]; | ||
276 | + } | ||
277 | + } | ||
278 | + Properties prop = loadProperties(); | ||
279 | + port = SortedProperties.getIntProperty(prop, | ||
280 | + "webPort", Constants.DEFAULT_HTTP_PORT); | ||
281 | + ssl = SortedProperties.getBooleanProperty(prop, | ||
282 | + "webSSL", false); | ||
283 | + allowOthers = SortedProperties.getBooleanProperty(prop, | ||
284 | + "webAllowOthers", false); | ||
285 | + commandHistoryString = prop.getProperty(COMMAND_HISTORY); | ||
286 | + for (int i = 0; args != null && i < args.length; i++) { | ||
287 | + String a = args[i]; | ||
288 | + if (Tool.isOption(a, "-webPort")) { | ||
289 | + port = Integer.decode(args[++i]); | ||
290 | + } else if (Tool.isOption(a, "-webSSL")) { | ||
291 | + ssl = true; | ||
292 | + } else if (Tool.isOption(a, "-webAllowOthers")) { | ||
293 | + allowOthers = true; | ||
294 | + } else if (Tool.isOption(a, "-webDaemon")) { | ||
295 | + isDaemon = true; | ||
296 | + } else if (Tool.isOption(a, "-baseDir")) { | ||
297 | + String baseDir = args[++i]; | ||
298 | + SysProperties.setBaseDir(baseDir); | ||
299 | + } else if (Tool.isOption(a, "-ifExists")) { | ||
300 | + ifExists = true; | ||
301 | + } else if (Tool.isOption(a, "-properties")) { | ||
302 | + // already set | ||
303 | + i++; | ||
304 | + } else if (Tool.isOption(a, "-trace")) { | ||
305 | + trace = true; | ||
306 | + } | ||
307 | + } | ||
308 | +// if (driverList != null) { | ||
309 | +// try { | ||
310 | +// String[] drivers = | ||
311 | +// StringUtils.arraySplit(driverList, ',', false); | ||
312 | +// URL[] urls = new URL[drivers.length]; | ||
313 | +// for(int i=0; i<drivers.length; i++) { | ||
314 | +// urls[i] = new URL(drivers[i]); | ||
315 | +// } | ||
316 | +// urlClassLoader = URLClassLoader.newInstance(urls); | ||
317 | +// } catch (MalformedURLException e) { | ||
318 | +// TraceSystem.traceThrowable(e); | ||
319 | +// } | ||
320 | +// } | ||
321 | + for (String[] lang : LANGUAGES) { | ||
322 | + languages.add(lang[0]); | ||
323 | + } | ||
324 | + updateURL(); | ||
325 | + } | ||
326 | + | ||
327 | + @Override | ||
328 | + public String getURL() { | ||
329 | + updateURL(); | ||
330 | + return url; | ||
331 | + } | ||
332 | + | ||
333 | + private void updateURL() { | ||
334 | + try { | ||
335 | + url = (ssl ? "https" : "http") + "://" + | ||
336 | + NetUtils.getLocalAddress() + ":" + port; | ||
337 | + } catch (NoClassDefFoundError e) { | ||
338 | + // Google App Engine does not allow java.net.InetAddress | ||
339 | + } | ||
340 | + } | ||
341 | + | ||
342 | + @Override | ||
343 | + public void start() { | ||
344 | + serverSocket = NetUtils.createServerSocket(port, ssl); | ||
345 | + port = serverSocket.getLocalPort(); | ||
346 | + updateURL(); | ||
347 | + } | ||
348 | + | ||
349 | + @Override | ||
350 | + public void listen() { | ||
351 | + this.listenerThread = Thread.currentThread(); | ||
352 | + try { | ||
353 | + while (serverSocket != null) { | ||
354 | + Socket s = serverSocket.accept(); | ||
355 | + WebThread c = new WebThread(s, this); | ||
356 | + running.add(c); | ||
357 | + c.start(); | ||
358 | + } | ||
359 | + } catch (Exception e) { | ||
360 | + trace(e.toString()); | ||
361 | + } | ||
362 | + } | ||
363 | + | ||
364 | + @Override | ||
365 | + public boolean isRunning(boolean traceError) { | ||
366 | + if (serverSocket == null) { | ||
367 | + return false; | ||
368 | + } | ||
369 | + try { | ||
370 | + Socket s = NetUtils.createLoopbackSocket(port, ssl); | ||
371 | + s.close(); | ||
372 | + return true; | ||
373 | + } catch (Exception e) { | ||
374 | + if (traceError) { | ||
375 | + traceError(e); | ||
376 | + } | ||
377 | + return false; | ||
378 | + } | ||
379 | + } | ||
380 | + | ||
381 | + public boolean isStopped() { | ||
382 | + return serverSocket == null; | ||
383 | + } | ||
384 | + | ||
385 | + @Override | ||
386 | + public void stop() { | ||
387 | + if (serverSocket != null) { | ||
388 | + try { | ||
389 | + serverSocket.close(); | ||
390 | + } catch (IOException e) { | ||
391 | + traceError(e); | ||
392 | + } | ||
393 | + serverSocket = null; | ||
394 | + } | ||
395 | + if (listenerThread != null) { | ||
396 | + try { | ||
397 | + listenerThread.join(1000); | ||
398 | + } catch (InterruptedException e) { | ||
399 | + DbException.traceThrowable(e); | ||
400 | + } | ||
401 | + } | ||
402 | + // TODO server: using a boolean 'now' argument? a timeout? | ||
403 | + for (WebSession session : New.arrayList(sessions.values())) { | ||
404 | + session.close(); | ||
405 | + } | ||
406 | + for (WebThread c : New.arrayList(running)) { | ||
407 | + try { | ||
408 | + c.stopNow(); | ||
409 | + c.join(100); | ||
410 | + } catch (Exception e) { | ||
411 | + traceError(e); | ||
412 | + } | ||
413 | + } | ||
414 | + } | ||
415 | + | ||
416 | + /** | ||
417 | + * Write trace information if trace is enabled. | ||
418 | + * | ||
419 | + * @param s the message to write | ||
420 | + */ | ||
421 | + void trace(String s) { | ||
422 | + if (trace) { | ||
423 | + System.out.println(s); | ||
424 | + } | ||
425 | + } | ||
426 | + | ||
427 | + /** | ||
428 | + * Write the stack trace if trace is enabled. | ||
429 | + * | ||
430 | + * @param e the exception | ||
431 | + */ | ||
432 | + void traceError(Throwable e) { | ||
433 | + if (trace) { | ||
434 | + e.printStackTrace(); | ||
435 | + } | ||
436 | + } | ||
437 | + | ||
438 | + /** | ||
439 | + * Check if this language is supported / translated. | ||
440 | + * | ||
441 | + * @param language the language | ||
442 | + * @return true if a translation is available | ||
443 | + */ | ||
444 | + boolean supportsLanguage(String language) { | ||
445 | + return languages.contains(language); | ||
446 | + } | ||
447 | + | ||
448 | + /** | ||
449 | + * Read the translation for this language and save them in the 'text' | ||
450 | + * property of this session. | ||
451 | + * | ||
452 | + * @param session the session | ||
453 | + * @param language the language | ||
454 | + */ | ||
455 | + void readTranslations(WebSession session, String language) { | ||
456 | + Properties text = new Properties(); | ||
457 | + try { | ||
458 | + trace("translation: "+language); | ||
459 | + byte[] trans = getFile("_text_"+language+".prop"); | ||
460 | + trace(" "+new String(trans)); | ||
461 | + text = SortedProperties.fromLines(new String(trans, Constants.UTF8)); | ||
462 | + // remove starting # (if not translated yet) | ||
463 | + for (Entry<Object, Object> entry : text.entrySet()) { | ||
464 | + String value = (String) entry.getValue(); | ||
465 | + if (value.startsWith("#")) { | ||
466 | + entry.setValue(value.substring(1)); | ||
467 | + } | ||
468 | + } | ||
469 | + } catch (IOException e) { | ||
470 | + DbException.traceThrowable(e); | ||
471 | + } | ||
472 | + session.put("text", new HashMap<Object, Object>(text)); | ||
473 | + } | ||
474 | + | ||
475 | + ArrayList<HashMap<String, Object>> getSessions() { | ||
476 | + ArrayList<HashMap<String, Object>> list = New.arrayList(); | ||
477 | + for (WebSession s : sessions.values()) { | ||
478 | + list.add(s.getInfo()); | ||
479 | + } | ||
480 | + return list; | ||
481 | + } | ||
482 | + | ||
483 | + @Override | ||
484 | + public String getType() { | ||
485 | + return "Web Console"; | ||
486 | + } | ||
487 | + | ||
488 | + @Override | ||
489 | + public String getName() { | ||
490 | + return "H2 Console Server"; | ||
491 | + } | ||
492 | + | ||
493 | + void setAllowOthers(boolean b) { | ||
494 | + allowOthers = b; | ||
495 | + } | ||
496 | + | ||
497 | + @Override | ||
498 | + public boolean getAllowOthers() { | ||
499 | + return allowOthers; | ||
500 | + } | ||
501 | + | ||
502 | + void setSSL(boolean b) { | ||
503 | + ssl = b; | ||
504 | + } | ||
505 | + | ||
506 | + void setPort(int port) { | ||
507 | + this.port = port; | ||
508 | + } | ||
509 | + | ||
510 | + boolean getSSL() { | ||
511 | + return ssl; | ||
512 | + } | ||
513 | + | ||
514 | + @Override | ||
515 | + public int getPort() { | ||
516 | + return port; | ||
517 | + } | ||
518 | + | ||
519 | + public boolean isCommandHistoryAllowed() { | ||
520 | + return commandHistoryString != null; | ||
521 | + } | ||
522 | + | ||
523 | + public void setCommandHistoryAllowed(boolean allowed) { | ||
524 | + if (allowed) { | ||
525 | + if (commandHistoryString == null) { | ||
526 | + commandHistoryString = ""; | ||
527 | + } | ||
528 | + } else { | ||
529 | + commandHistoryString = null; | ||
530 | + } | ||
531 | + } | ||
532 | + | ||
533 | + public ArrayList<String> getCommandHistoryList() { | ||
534 | + ArrayList<String> result = New.arrayList(); | ||
535 | + if (commandHistoryString == null) { | ||
536 | + return result; | ||
537 | + } | ||
538 | + | ||
539 | + // Split the commandHistoryString on non-escaped semicolons | ||
540 | + // and unescape it. | ||
541 | + StringBuilder sb = new StringBuilder(); | ||
542 | + for (int end = 0;; end++) { | ||
543 | + if (end == commandHistoryString.length() || | ||
544 | + commandHistoryString.charAt(end) == ';') { | ||
545 | + if (sb.length() > 0) { | ||
546 | + result.add(sb.toString()); | ||
547 | + sb.delete(0, sb.length()); | ||
548 | + } | ||
549 | + if (end == commandHistoryString.length()) { | ||
550 | + break; | ||
551 | + } | ||
552 | + } else if (commandHistoryString.charAt(end) == '\\' && | ||
553 | + end < commandHistoryString.length() - 1) { | ||
554 | + sb.append(commandHistoryString.charAt(++end)); | ||
555 | + } else { | ||
556 | + sb.append(commandHistoryString.charAt(end)); | ||
557 | + } | ||
558 | + } | ||
559 | + return result; | ||
560 | + } | ||
561 | + | ||
562 | + /** | ||
563 | + * Save the command history to the properties file. | ||
564 | + * | ||
565 | + * @param commandHistory the history | ||
566 | + */ | ||
567 | + public void saveCommandHistoryList(ArrayList<String> commandHistory) { | ||
568 | + StringBuilder sb = new StringBuilder(); | ||
569 | + for (String s : commandHistory) { | ||
570 | + if (sb.length() > 0) { | ||
571 | + sb.append(';'); | ||
572 | + } | ||
573 | + sb.append(s.replace("\\", "\\\\").replace(";", "\\;")); | ||
574 | + } | ||
575 | + commandHistoryString = sb.toString(); | ||
576 | + saveProperties(null); | ||
577 | + } | ||
578 | + | ||
579 | + /** | ||
580 | + * Get the connection information for this setting. | ||
581 | + * | ||
582 | + * @param name the setting name | ||
583 | + * @return the connection information | ||
584 | + */ | ||
585 | + ConnectionInfo getSetting(String name) { | ||
586 | + return connInfoMap.get(name); | ||
587 | + } | ||
588 | + | ||
589 | + /** | ||
590 | + * Update a connection information setting. | ||
591 | + * | ||
592 | + * @param info the connection information | ||
593 | + */ | ||
594 | + void updateSetting(ConnectionInfo info) { | ||
595 | + connInfoMap.put(info.name, info); | ||
596 | + info.lastAccess = ticker++; | ||
597 | + } | ||
598 | + | ||
599 | + /** | ||
600 | + * Remove a connection information setting from the list | ||
601 | + * | ||
602 | + * @param name the setting to remove | ||
603 | + */ | ||
604 | + void removeSetting(String name) { | ||
605 | + connInfoMap.remove(name); | ||
606 | + } | ||
607 | + | ||
608 | + private Properties loadProperties() { | ||
609 | + try { | ||
610 | + if ("null".equals(serverPropertiesDir)) { | ||
611 | + return new Properties(); | ||
612 | + } | ||
613 | + return SortedProperties.loadProperties( | ||
614 | + serverPropertiesDir + "/" + Constants.SERVER_PROPERTIES_NAME); | ||
615 | + } catch (Exception e) { | ||
616 | + DbException.traceThrowable(e); | ||
617 | + return new Properties(); | ||
618 | + } | ||
619 | + } | ||
620 | + | ||
621 | + /** | ||
622 | + * Get the list of connection information setting names. | ||
623 | + * | ||
624 | + * @return the connection info names | ||
625 | + */ | ||
626 | + String[] getSettingNames() { | ||
627 | + ArrayList<ConnectionInfo> list = getSettings(); | ||
628 | + String[] names = new String[list.size()]; | ||
629 | + for (int i = 0; i < list.size(); i++) { | ||
630 | + names[i] = list.get(i).name; | ||
631 | + } | ||
632 | + return names; | ||
633 | + } | ||
634 | + | ||
635 | + /** | ||
636 | + * Get the list of connection info objects. | ||
637 | + * | ||
638 | + * @return the list | ||
639 | + */ | ||
640 | + synchronized ArrayList<ConnectionInfo> getSettings() { | ||
641 | + ArrayList<ConnectionInfo> settings = New.arrayList(); | ||
642 | + if (connInfoMap.size() == 0) { | ||
643 | + Properties prop = loadProperties(); | ||
644 | + if (prop.size() == 0) { | ||
645 | + for (String gen : GENERIC) { | ||
646 | + ConnectionInfo info = new ConnectionInfo(gen); | ||
647 | + settings.add(info); | ||
648 | + updateSetting(info); | ||
649 | + } | ||
650 | + } else { | ||
651 | + for (int i = 0;; i++) { | ||
652 | + String data = prop.getProperty(String.valueOf(i)); | ||
653 | + if (data == null) { | ||
654 | + break; | ||
655 | + } | ||
656 | + ConnectionInfo info = new ConnectionInfo(data); | ||
657 | + settings.add(info); | ||
658 | + updateSetting(info); | ||
659 | + } | ||
660 | + } | ||
661 | + } else { | ||
662 | + settings.addAll(connInfoMap.values()); | ||
663 | + } | ||
664 | + Collections.sort(settings); | ||
665 | + return settings; | ||
666 | + } | ||
667 | + | ||
668 | + /** | ||
669 | + * Save the settings to the properties file. | ||
670 | + * | ||
671 | + * @param prop null or the properties webPort, webAllowOthers, and webSSL | ||
672 | + */ | ||
673 | + synchronized void saveProperties(Properties prop) { | ||
674 | + try { | ||
675 | + if (prop == null) { | ||
676 | + Properties old = loadProperties(); | ||
677 | + prop = new SortedProperties(); | ||
678 | + prop.setProperty("webPort", | ||
679 | + "" + SortedProperties.getIntProperty(old, | ||
680 | + "webPort", port)); | ||
681 | + prop.setProperty("webAllowOthers", | ||
682 | + "" + SortedProperties.getBooleanProperty(old, | ||
683 | + "webAllowOthers", allowOthers)); | ||
684 | + prop.setProperty("webSSL", | ||
685 | + "" + SortedProperties.getBooleanProperty(old, | ||
686 | + "webSSL", ssl)); | ||
687 | + if (commandHistoryString != null) { | ||
688 | + prop.setProperty(COMMAND_HISTORY, commandHistoryString); | ||
689 | + } | ||
690 | + } | ||
691 | + ArrayList<ConnectionInfo> settings = getSettings(); | ||
692 | + int len = settings.size(); | ||
693 | + for (int i = 0; i < len; i++) { | ||
694 | + ConnectionInfo info = settings.get(i); | ||
695 | + if (info != null) { | ||
696 | + prop.setProperty(String.valueOf(len - i - 1), info.getString()); | ||
697 | + } | ||
698 | + } | ||
699 | + if (!"null".equals(serverPropertiesDir)) { | ||
700 | + OutputStream out = FileUtils.newOutputStream( | ||
701 | + serverPropertiesDir + "/" + Constants.SERVER_PROPERTIES_NAME, false); | ||
702 | + prop.store(out, "H2 Server Properties"); | ||
703 | + out.close(); | ||
704 | + } | ||
705 | + } catch (Exception e) { | ||
706 | + DbException.traceThrowable(e); | ||
707 | + } | ||
708 | + } | ||
709 | + | ||
710 | + /** | ||
711 | + * Open a database connection. | ||
712 | + * | ||
713 | + * @param driver the driver class name | ||
714 | + * @param databaseUrl the database URL | ||
715 | + * @param user the user name | ||
716 | + * @param password the password | ||
717 | + * @return the database connection | ||
718 | + */ | ||
719 | + Connection getConnection(String driver, String databaseUrl, String user, | ||
720 | + String password) throws SQLException { | ||
721 | + driver = driver.trim(); | ||
722 | + databaseUrl = databaseUrl.trim(); | ||
723 | + org.h2.Driver.load(); | ||
724 | + Properties p = new Properties(); | ||
725 | + p.setProperty("user", user.trim()); | ||
726 | + // do not trim the password, otherwise an | ||
727 | + // encrypted H2 database with empty user password doesn't work | ||
728 | + p.setProperty("password", password); | ||
729 | + if (databaseUrl.startsWith("jdbc:h2:")) { | ||
730 | + if (ifExists) { | ||
731 | + databaseUrl += ";IFEXISTS=TRUE"; | ||
732 | + } | ||
733 | + // PostgreSQL would throw a NullPointerException | ||
734 | + // if it is loaded before the H2 driver | ||
735 | + // because it can't deal with non-String objects in the connection | ||
736 | + // Properties | ||
737 | + return org.h2.Driver.load().connect(databaseUrl, p); | ||
738 | + } | ||
739 | +// try { | ||
740 | +// Driver dr = (Driver) urlClassLoader. | ||
741 | +// loadClass(driver).newInstance(); | ||
742 | +// return dr.connect(url, p); | ||
743 | +// } catch(ClassNotFoundException e2) { | ||
744 | +// throw e2; | ||
745 | +// } | ||
746 | + return JdbcUtils.getConnection(driver, databaseUrl, p); | ||
747 | + } | ||
748 | + | ||
749 | + /** | ||
750 | + * Shut down the web server. | ||
751 | + */ | ||
752 | + void shutdown() { | ||
753 | + if (shutdownHandler != null) { | ||
754 | + shutdownHandler.shutdown(); | ||
755 | + } | ||
756 | + } | ||
757 | + | ||
758 | + public void setShutdownHandler(ShutdownHandler shutdownHandler) { | ||
759 | + this.shutdownHandler = shutdownHandler; | ||
760 | + } | ||
761 | + | ||
762 | + /** | ||
763 | + * Create a session with a given connection. | ||
764 | + * | ||
765 | + * @param conn the connection | ||
766 | + * @return the URL of the web site to access this connection | ||
767 | + */ | ||
768 | + public String addSession(Connection conn) throws SQLException { | ||
769 | + WebSession session = createNewSession("local"); | ||
770 | + session.setShutdownServerOnDisconnect(); | ||
771 | + session.setConnection(conn); | ||
772 | + session.put("url", conn.getMetaData().getURL()); | ||
773 | + String s = (String) session.get("sessionId"); | ||
774 | + return url + "/frame.jsp?jsessionid=" + s; | ||
775 | + } | ||
776 | + | ||
777 | + /** | ||
778 | + * The translate thread reads and writes the file translation.properties | ||
779 | + * once a second. | ||
780 | + */ | ||
781 | + private class TranslateThread extends Thread { | ||
782 | + | ||
783 | + private final File file = new File("translation.properties"); | ||
784 | + private final Map<Object, Object> translation; | ||
785 | + private volatile boolean stopNow; | ||
786 | + | ||
787 | + TranslateThread(Map<Object, Object> translation) { | ||
788 | + this.translation = translation; | ||
789 | + } | ||
790 | + | ||
791 | + public String getFileName() { | ||
792 | + return file.getAbsolutePath(); | ||
793 | + } | ||
794 | + | ||
795 | + public void stopNow() { | ||
796 | + this.stopNow = true; | ||
797 | + try { | ||
798 | + join(); | ||
799 | + } catch (InterruptedException e) { | ||
800 | + // ignore | ||
801 | + } | ||
802 | + } | ||
803 | + | ||
804 | + @Override | ||
805 | + public void run() { | ||
806 | + while (!stopNow) { | ||
807 | + try { | ||
808 | + SortedProperties sp = new SortedProperties(); | ||
809 | + if (file.exists()) { | ||
810 | + InputStream in = FileUtils.newInputStream(file.getName()); | ||
811 | + sp.load(in); | ||
812 | + translation.putAll(sp); | ||
813 | + } else { | ||
814 | + OutputStream out = FileUtils.newOutputStream(file.getName(), false); | ||
815 | + sp.putAll(translation); | ||
816 | + sp.store(out, "Translation"); | ||
817 | + } | ||
818 | + Thread.sleep(1000); | ||
819 | + } catch (Exception e) { | ||
820 | + traceError(e); | ||
821 | + } | ||
822 | + } | ||
823 | + } | ||
824 | + | ||
825 | + } | ||
826 | + | ||
827 | + /** | ||
828 | + * Start the translation thread that reads the file once a second. | ||
829 | + * | ||
830 | + * @param translation the translation map | ||
831 | + * @return the name of the file to translate | ||
832 | + */ | ||
833 | + String startTranslate(Map<Object, Object> translation) { | ||
834 | + if (translateThread != null) { | ||
835 | + translateThread.stopNow(); | ||
836 | + } | ||
837 | + translateThread = new TranslateThread(translation); | ||
838 | + translateThread.setDaemon(true); | ||
839 | + translateThread.start(); | ||
840 | + return translateThread.getFileName(); | ||
841 | + } | ||
842 | + | ||
843 | + @Override | ||
844 | + public boolean isDaemon() { | ||
845 | + return isDaemon; | ||
846 | + } | ||
847 | + | ||
848 | + void setAllowChunked(boolean allowChunked) { | ||
849 | + this.allowChunked = allowChunked; | ||
850 | + } | ||
851 | + | ||
852 | + boolean getAllowChunked() { | ||
853 | + return allowChunked; | ||
854 | + } | ||
855 | + | ||
856 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +package org.legrog.util; | ||
2 | +// Code copied from org.h2.server.web.WebSession | ||
3 | +/* | ||
4 | + * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, | ||
5 | + * and the EPL 1.0 (http://h2database.com/html/license.html). | ||
6 | + * Initial Developer: H2 Group | ||
7 | + */ | ||
8 | +///package org.h2.server.web; | ||
9 | + | ||
10 | + import java.sql.Connection; | ||
11 | + import java.sql.DatabaseMetaData; | ||
12 | + import java.sql.ResultSet; | ||
13 | + import java.sql.SQLException; | ||
14 | + import java.sql.Statement; | ||
15 | + import java.sql.Timestamp; | ||
16 | + import java.util.ArrayList; | ||
17 | + import java.util.HashMap; | ||
18 | + import java.util.Locale; | ||
19 | + | ||
20 | + import org.h2.bnf.Bnf; | ||
21 | + import org.h2.bnf.context.DbContents; | ||
22 | + import org.h2.bnf.context.DbContextRule; | ||
23 | + import org.h2.message.DbException; | ||
24 | + import org.h2.util.New; | ||
25 | +import org.h2.server.web.*; | ||
26 | + | ||
27 | +/** | ||
28 | + * The web session keeps all data of a user session. | ||
29 | + * This class is used by the H2 Console. | ||
30 | + */ | ||
31 | +class WebSession { | ||
32 | + | ||
33 | + private static final int MAX_HISTORY = 1000; | ||
34 | + | ||
35 | + /** | ||
36 | + * The last time this client sent a request. | ||
37 | + */ | ||
38 | + long lastAccess; | ||
39 | + | ||
40 | + /** | ||
41 | + * The session attribute map. | ||
42 | + */ | ||
43 | + final HashMap<String, Object> map = New.hashMap(); | ||
44 | + | ||
45 | + /** | ||
46 | + * The current locale. | ||
47 | + */ | ||
48 | + Locale locale; | ||
49 | + | ||
50 | + /** | ||
51 | + * The currently executing statement. | ||
52 | + */ | ||
53 | + Statement executingStatement; | ||
54 | + | ||
55 | + /** | ||
56 | + * The current updatable result set. | ||
57 | + */ | ||
58 | + ResultSet result; | ||
59 | + | ||
60 | + private final WebServer server; | ||
61 | + | ||
62 | + private final ArrayList<String> commandHistory; | ||
63 | + | ||
64 | + private Connection conn; | ||
65 | + private DatabaseMetaData meta; | ||
66 | + private DbContents contents = new DbContents(); | ||
67 | + private Bnf bnf; | ||
68 | + private boolean shutdownServerOnDisconnect; | ||
69 | + | ||
70 | + WebSession(WebServer server) { | ||
71 | + this.server = server; | ||
72 | + // This must be stored in the session rather than in the server. | ||
73 | + // Otherwise, one client could allow | ||
74 | + // saving history for others (insecure). | ||
75 | + this.commandHistory = server.getCommandHistoryList(); | ||
76 | + } | ||
77 | + | ||
78 | + /** | ||
79 | + * Put an attribute value in the map. | ||
80 | + * | ||
81 | + * @param key the key | ||
82 | + * @param value the new value | ||
83 | + */ | ||
84 | + void put(String key, Object value) { | ||
85 | + map.put(key, value); | ||
86 | + } | ||
87 | + | ||
88 | + /** | ||
89 | + * Get the value for the given key. | ||
90 | + * | ||
91 | + * @param key the key | ||
92 | + * @return the value | ||
93 | + */ | ||
94 | + Object get(String key) { | ||
95 | + if ("sessions".equals(key)) { | ||
96 | + return server.getSessions(); | ||
97 | + } | ||
98 | + return map.get(key); | ||
99 | + } | ||
100 | + | ||
101 | + /** | ||
102 | + * Remove a session attribute from the map. | ||
103 | + * | ||
104 | + * @param key the key | ||
105 | + */ | ||
106 | + void remove(String key) { | ||
107 | + map.remove(key); | ||
108 | + } | ||
109 | + | ||
110 | + /** | ||
111 | + * Get the BNF object. | ||
112 | + * | ||
113 | + * @return the BNF object | ||
114 | + */ | ||
115 | + Bnf getBnf() { | ||
116 | + return bnf; | ||
117 | + } | ||
118 | + | ||
119 | + /** | ||
120 | + * Load the SQL grammar BNF. | ||
121 | + */ | ||
122 | + void loadBnf() { | ||
123 | + try { | ||
124 | + Bnf newBnf = Bnf.getInstance(null); | ||
125 | + DbContextRule columnRule = | ||
126 | + new DbContextRule(contents, DbContextRule.COLUMN); | ||
127 | + DbContextRule newAliasRule = | ||
128 | + new DbContextRule(contents, DbContextRule.NEW_TABLE_ALIAS); | ||
129 | + DbContextRule aliasRule = | ||
130 | + new DbContextRule(contents, DbContextRule.TABLE_ALIAS); | ||
131 | + DbContextRule tableRule = | ||
132 | + new DbContextRule(contents, DbContextRule.TABLE); | ||
133 | + DbContextRule schemaRule = | ||
134 | + new DbContextRule(contents, DbContextRule.SCHEMA); | ||
135 | + DbContextRule columnAliasRule = | ||
136 | + new DbContextRule(contents, DbContextRule.COLUMN_ALIAS); | ||
137 | + DbContextRule procedure = | ||
138 | + new DbContextRule(contents, DbContextRule.PROCEDURE); | ||
139 | + newBnf.updateTopic("procedure", procedure); | ||
140 | + newBnf.updateTopic("column_name", columnRule); | ||
141 | + newBnf.updateTopic("new_table_alias", newAliasRule); | ||
142 | + newBnf.updateTopic("table_alias", aliasRule); | ||
143 | + newBnf.updateTopic("column_alias", columnAliasRule); | ||
144 | + newBnf.updateTopic("table_name", tableRule); | ||
145 | + newBnf.updateTopic("schema_name", schemaRule); | ||
146 | + newBnf.linkStatements(); | ||
147 | + bnf = newBnf; | ||
148 | + } catch (Exception e) { | ||
149 | + // ok we don't have the bnf | ||
150 | + server.traceError(e); | ||
151 | + } | ||
152 | + } | ||
153 | + | ||
154 | + /** | ||
155 | + * Get the SQL statement from history. | ||
156 | + * | ||
157 | + * @param id the history id | ||
158 | + * @return the SQL statement | ||
159 | + */ | ||
160 | + String getCommand(int id) { | ||
161 | + return commandHistory.get(id); | ||
162 | + } | ||
163 | + | ||
164 | + /** | ||
165 | + * Add a SQL statement to the history. | ||
166 | + * | ||
167 | + * @param sql the SQL statement | ||
168 | + */ | ||
169 | + void addCommand(String sql) { | ||
170 | + if (sql == null) { | ||
171 | + return; | ||
172 | + } | ||
173 | + sql = sql.trim(); | ||
174 | + if (sql.length() == 0) { | ||
175 | + return; | ||
176 | + } | ||
177 | + if (commandHistory.size() > MAX_HISTORY) { | ||
178 | + commandHistory.remove(0); | ||
179 | + } | ||
180 | + int idx = commandHistory.indexOf(sql); | ||
181 | + if (idx >= 0) { | ||
182 | + commandHistory.remove(idx); | ||
183 | + } | ||
184 | + commandHistory.add(sql); | ||
185 | + if (server.isCommandHistoryAllowed()) { | ||
186 | + server.saveCommandHistoryList(commandHistory); | ||
187 | + } | ||
188 | + } | ||
189 | + | ||
190 | + /** | ||
191 | + * Get the list of SQL statements in the history. | ||
192 | + * | ||
193 | + * @return the commands | ||
194 | + */ | ||
195 | + ArrayList<String> getCommandHistory() { | ||
196 | + return commandHistory; | ||
197 | + } | ||
198 | + | ||
199 | + /** | ||
200 | + * Update session meta data information and get the information in a map. | ||
201 | + * | ||
202 | + * @return a map containing the session meta data | ||
203 | + */ | ||
204 | + HashMap<String, Object> getInfo() { | ||
205 | + HashMap<String, Object> m = New.hashMap(); | ||
206 | + m.putAll(map); | ||
207 | + m.put("lastAccess", new Timestamp(lastAccess).toString()); | ||
208 | + try { | ||
209 | + m.put("url", conn == null ? | ||
210 | + "${text.admin.notConnected}" : conn.getMetaData().getURL()); | ||
211 | + m.put("user", conn == null ? | ||
212 | + "-" : conn.getMetaData().getUserName()); | ||
213 | + m.put("lastQuery", commandHistory.size() == 0 ? | ||
214 | + "" : commandHistory.get(0)); | ||
215 | + m.put("executing", executingStatement == null ? | ||
216 | + "${text.admin.no}" : "${text.admin.yes}"); | ||
217 | + } catch (SQLException e) { | ||
218 | + DbException.traceThrowable(e); | ||
219 | + } | ||
220 | + return m; | ||
221 | + } | ||
222 | + | ||
223 | + void setConnection(Connection conn) throws SQLException { | ||
224 | + this.conn = conn; | ||
225 | + if (conn == null) { | ||
226 | + meta = null; | ||
227 | + } else { | ||
228 | + meta = conn.getMetaData(); | ||
229 | + } | ||
230 | + contents = new DbContents(); | ||
231 | + } | ||
232 | + | ||
233 | + DatabaseMetaData getMetaData() { | ||
234 | + return meta; | ||
235 | + } | ||
236 | + | ||
237 | + Connection getConnection() { | ||
238 | + return conn; | ||
239 | + } | ||
240 | + | ||
241 | + DbContents getContents() { | ||
242 | + return contents; | ||
243 | + } | ||
244 | + | ||
245 | + /** | ||
246 | + * Shutdown the server when disconnecting. | ||
247 | + */ | ||
248 | + void setShutdownServerOnDisconnect() { | ||
249 | + this.shutdownServerOnDisconnect = true; | ||
250 | + } | ||
251 | + | ||
252 | + boolean getShutdownServerOnDisconnect() { | ||
253 | + return shutdownServerOnDisconnect; | ||
254 | + } | ||
255 | + | ||
256 | + /** | ||
257 | + * Close the connection and stop the statement if one is currently | ||
258 | + * executing. | ||
259 | + */ | ||
260 | + void close() { | ||
261 | + if (executingStatement != null) { | ||
262 | + try { | ||
263 | + executingStatement.cancel(); | ||
264 | + } catch (Exception e) { | ||
265 | + // ignore | ||
266 | + } | ||
267 | + } | ||
268 | + if (conn != null) { | ||
269 | + try { | ||
270 | + conn.close(); | ||
271 | + } catch (Exception e) { | ||
272 | + // ignore | ||
273 | + } | ||
274 | + } | ||
275 | + | ||
276 | + } | ||
277 | + | ||
278 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
src/main/java/org/legrog/util/WebThread.java
0 → 100644
1 | +package org.legrog.util; | ||
2 | +// Code copied from org.h2.server.web.WebThread | ||
3 | +/* | ||
4 | + * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, | ||
5 | + * and the EPL 1.0 (http://h2database.com/html/license.html). | ||
6 | + * Initial Developer: H2 Group | ||
7 | + */ | ||
8 | +//package org.h2.server.web; | ||
9 | + | ||
10 | + import java.io.BufferedInputStream; | ||
11 | + import java.io.BufferedOutputStream; | ||
12 | + import java.io.IOException; | ||
13 | + import java.io.InputStream; | ||
14 | + import java.io.OutputStream; | ||
15 | + import java.net.Socket; | ||
16 | + import java.net.UnknownHostException; | ||
17 | + import java.util.Iterator; | ||
18 | + import java.util.Locale; | ||
19 | + import java.util.Properties; | ||
20 | + import java.util.StringTokenizer; | ||
21 | + import org.h2.engine.Constants; | ||
22 | + import org.h2.engine.SysProperties; | ||
23 | + import org.h2.message.DbException; | ||
24 | + import org.h2.mvstore.DataUtils; | ||
25 | + import org.h2.util.IOUtils; | ||
26 | + import org.h2.util.NetUtils; | ||
27 | + import org.h2.util.StringUtils; | ||
28 | +//import org.h2.server.web.*; | ||
29 | + | ||
30 | +/** | ||
31 | + * For each connection to a session, an object of this class is created. | ||
32 | + * This class is used by the H2 Console. | ||
33 | + */ | ||
34 | +class WebThread extends WebApp implements Runnable { | ||
35 | + | ||
36 | + protected OutputStream output; | ||
37 | + protected final Socket socket; | ||
38 | + private final Thread thread; | ||
39 | + private InputStream input; | ||
40 | + private String ifModifiedSince; | ||
41 | + | ||
42 | + WebThread(Socket socket, WebServer server) { | ||
43 | + super(server); | ||
44 | + this.socket = socket; | ||
45 | + thread = new Thread(this, "H2 Console thread"); | ||
46 | + } | ||
47 | + | ||
48 | + /** | ||
49 | + * Start the thread. | ||
50 | + */ | ||
51 | + void start() { | ||
52 | + thread.start(); | ||
53 | + } | ||
54 | + | ||
55 | + /** | ||
56 | + * Wait until the thread is stopped. | ||
57 | + * | ||
58 | + * @param millis the maximum number of milliseconds to wait | ||
59 | + */ | ||
60 | + void join(int millis) throws InterruptedException { | ||
61 | + thread.join(millis); | ||
62 | + } | ||
63 | + | ||
64 | + /** | ||
65 | + * Close the connection now. | ||
66 | + */ | ||
67 | + void stopNow() { | ||
68 | + this.stop = true; | ||
69 | + try { | ||
70 | + socket.close(); | ||
71 | + } catch (IOException e) { | ||
72 | + // ignore | ||
73 | + } | ||
74 | + } | ||
75 | + | ||
76 | + private String getAllowedFile(String requestedFile) { | ||
77 | + if (!allow()) { | ||
78 | + return "notAllowed.jsp"; | ||
79 | + } | ||
80 | + if (requestedFile.length() == 0) { | ||
81 | + return "index.do"; | ||
82 | + } | ||
83 | + return requestedFile; | ||
84 | + } | ||
85 | + | ||
86 | + @Override | ||
87 | + public void run() { | ||
88 | + try { | ||
89 | + input = new BufferedInputStream(socket.getInputStream()); | ||
90 | + output = new BufferedOutputStream(socket.getOutputStream()); | ||
91 | + while (!stop) { | ||
92 | + if (!process()) { | ||
93 | + break; | ||
94 | + } | ||
95 | + } | ||
96 | + } catch (Exception e) { | ||
97 | + DbException.traceThrowable(e); | ||
98 | + } | ||
99 | + IOUtils.closeSilently(output); | ||
100 | + IOUtils.closeSilently(input); | ||
101 | + try { | ||
102 | + socket.close(); | ||
103 | + } catch (IOException e) { | ||
104 | + // ignore | ||
105 | + } finally { | ||
106 | + server.remove(this); | ||
107 | + } | ||
108 | + } | ||
109 | + | ||
110 | + @SuppressWarnings("unchecked") | ||
111 | + private boolean process() throws IOException { | ||
112 | + boolean keepAlive = false; | ||
113 | + String head = readHeaderLine(); | ||
114 | + if (head.startsWith("GET ") || head.startsWith("POST ")) { | ||
115 | + int begin = head.indexOf('/'), end = head.lastIndexOf(' '); | ||
116 | + String file; | ||
117 | + if (begin < 0 || end < begin) { | ||
118 | + file = ""; | ||
119 | + } else { | ||
120 | + file = head.substring(begin + 1, end).trim(); | ||
121 | + } | ||
122 | + trace(head + ": " + file); | ||
123 | + file = getAllowedFile(file); | ||
124 | + attributes = new Properties(); | ||
125 | + int paramIndex = file.indexOf("?"); | ||
126 | + session = null; | ||
127 | + if (paramIndex >= 0) { | ||
128 | + String attrib = file.substring(paramIndex + 1); | ||
129 | + parseAttributes(attrib); | ||
130 | + String sessionId = attributes.getProperty("jsessionid"); | ||
131 | + file = file.substring(0, paramIndex); | ||
132 | + session = server.getSession(sessionId); | ||
133 | + } | ||
134 | + keepAlive = parseHeader(); | ||
135 | + String hostAddr = socket.getInetAddress().getHostAddress(); | ||
136 | + file = processRequest(file, hostAddr); | ||
137 | + if (file.length() == 0) { | ||
138 | + // asynchronous request | ||
139 | + return true; | ||
140 | + } | ||
141 | + String message; | ||
142 | + byte[] bytes; | ||
143 | + if (cache && ifModifiedSince != null && | ||
144 | + ifModifiedSince.equals(server.getStartDateTime())) { | ||
145 | + bytes = null; | ||
146 | + message = "HTTP/1.1 304 Not Modified\r\n"; | ||
147 | + } else { | ||
148 | + bytes = server.getFile(file); | ||
149 | + if (bytes == null) { | ||
150 | + message = "HTTP/1.1 404 Not Found\r\n"; | ||
151 | + bytes = ("File not found: " + file).getBytes(Constants.UTF8); | ||
152 | + message += "Content-Length: " + bytes.length + "\r\n"; | ||
153 | + } else { | ||
154 | + if (session != null && file.endsWith(".jsp")) { | ||
155 | + String page = new String(bytes, Constants.UTF8); | ||
156 | + if (SysProperties.CONSOLE_STREAM) { | ||
157 | + Iterator<String> it = (Iterator<String>) session.map.remove("chunks"); | ||
158 | + if (it != null) { | ||
159 | + message = "HTTP/1.1 200 OK\r\n"; | ||
160 | + message += "Content-Type: " + mimeType + "\r\n"; | ||
161 | + message += "Cache-Control: no-cache\r\n"; | ||
162 | + message += "Transfer-Encoding: chunked\r\n"; | ||
163 | + message += "\r\n"; | ||
164 | + trace(message); | ||
165 | + output.write(message.getBytes()); | ||
166 | + while (it.hasNext()) { | ||
167 | + String s = it.next(); | ||
168 | + s = PageParser.parse(s, session.map); | ||
169 | + bytes = s.getBytes(Constants.UTF8); | ||
170 | + if (bytes.length == 0) { | ||
171 | + continue; | ||
172 | + } | ||
173 | + output.write(Integer.toHexString(bytes.length).getBytes()); | ||
174 | + output.write("\r\n".getBytes()); | ||
175 | + output.write(bytes); | ||
176 | + output.write("\r\n".getBytes()); | ||
177 | + output.flush(); | ||
178 | + } | ||
179 | + output.write("0\r\n\r\n".getBytes()); | ||
180 | + output.flush(); | ||
181 | + return keepAlive; | ||
182 | + } | ||
183 | + } | ||
184 | + page = PageParser.parse(page, session.map); | ||
185 | + bytes = page.getBytes(Constants.UTF8); | ||
186 | + } | ||
187 | + message = "HTTP/1.1 200 OK\r\n"; | ||
188 | + message += "Content-Type: " + mimeType + "\r\n"; | ||
189 | + if (!cache) { | ||
190 | + message += "Cache-Control: no-cache\r\n"; | ||
191 | + } else { | ||
192 | + message += "Cache-Control: max-age=10\r\n"; | ||
193 | + message += "Last-Modified: " + server.getStartDateTime() + "\r\n"; | ||
194 | + } | ||
195 | + message += "Content-Length: " + bytes.length + "\r\n"; | ||
196 | + } | ||
197 | + } | ||
198 | + message += "\r\n"; | ||
199 | + trace(message); | ||
200 | + output.write(message.getBytes()); | ||
201 | + if (bytes != null) { | ||
202 | + output.write(bytes); | ||
203 | + } | ||
204 | + output.flush(); | ||
205 | + } | ||
206 | + return keepAlive; | ||
207 | + } | ||
208 | + | ||
209 | + private String readHeaderLine() throws IOException { | ||
210 | + StringBuilder buff = new StringBuilder(); | ||
211 | + while (true) { | ||
212 | + int c = input.read(); | ||
213 | + if (c == -1) { | ||
214 | + throw new IOException("Unexpected EOF"); | ||
215 | + } else if (c == '\r') { | ||
216 | + if (input.read() == '\n') { | ||
217 | + return buff.length() > 0 ? buff.toString() : null; | ||
218 | + } | ||
219 | + } else if (c == '\n') { | ||
220 | + return buff.length() > 0 ? buff.toString() : null; | ||
221 | + } else { | ||
222 | + buff.append((char) c); | ||
223 | + } | ||
224 | + } | ||
225 | + } | ||
226 | + | ||
227 | + private void parseAttributes(String s) { | ||
228 | + trace("data=" + s); | ||
229 | + while (s != null) { | ||
230 | + int idx = s.indexOf('='); | ||
231 | + if (idx >= 0) { | ||
232 | + String property = s.substring(0, idx); | ||
233 | + s = s.substring(idx + 1); | ||
234 | + idx = s.indexOf('&'); | ||
235 | + String value; | ||
236 | + if (idx >= 0) { | ||
237 | + value = s.substring(0, idx); | ||
238 | + s = s.substring(idx + 1); | ||
239 | + } else { | ||
240 | + value = s; | ||
241 | + } | ||
242 | + String attr = StringUtils.urlDecode(value); | ||
243 | + attributes.put(property, attr); | ||
244 | + } else { | ||
245 | + break; | ||
246 | + } | ||
247 | + } | ||
248 | + trace(attributes.toString()); | ||
249 | + } | ||
250 | + | ||
251 | + private boolean parseHeader() throws IOException { | ||
252 | + boolean keepAlive = false; | ||
253 | + trace("parseHeader"); | ||
254 | + int len = 0; | ||
255 | + ifModifiedSince = null; | ||
256 | + boolean multipart = false; | ||
257 | + while (true) { | ||
258 | + String line = readHeaderLine(); | ||
259 | + if (line == null) { | ||
260 | + break; | ||
261 | + } | ||
262 | + trace(" " + line); | ||
263 | + String lower = StringUtils.toLowerEnglish(line); | ||
264 | + if (lower.startsWith("if-modified-since")) { | ||
265 | + ifModifiedSince = getHeaderLineValue(line); | ||
266 | + } else if (lower.startsWith("connection")) { | ||
267 | + String conn = getHeaderLineValue(line); | ||
268 | + if ("keep-alive".equals(conn)) { | ||
269 | + keepAlive = true; | ||
270 | + } | ||
271 | + } else if (lower.startsWith("content-type")) { | ||
272 | + String type = getHeaderLineValue(line); | ||
273 | + if (type.startsWith("multipart/form-data")) { | ||
274 | + multipart = true; | ||
275 | + } | ||
276 | + } else if (lower.startsWith("content-length")) { | ||
277 | + len = Integer.parseInt(getHeaderLineValue(line)); | ||
278 | + trace("len=" + len); | ||
279 | + } else if (lower.startsWith("user-agent")) { | ||
280 | + boolean isWebKit = lower.contains("webkit/"); | ||
281 | + if (isWebKit && session != null) { | ||
282 | + // workaround for what seems to be a WebKit bug: | ||
283 | + // http://code.google.com/p/chromium/issues/detail?id=6402 | ||
284 | + session.put("frame-border", "1"); | ||
285 | + session.put("frameset-border", "2"); | ||
286 | + } | ||
287 | + } else if (lower.startsWith("accept-language")) { | ||
288 | + Locale locale = session == null ? null : session.locale; | ||
289 | + if (locale == null) { | ||
290 | + String languages = getHeaderLineValue(line); | ||
291 | + StringTokenizer tokenizer = new StringTokenizer(languages, ",;"); | ||
292 | + while (tokenizer.hasMoreTokens()) { | ||
293 | + String token = tokenizer.nextToken(); | ||
294 | + if (!token.startsWith("q=")) { | ||
295 | + if (server.supportsLanguage(token)) { | ||
296 | + int dash = token.indexOf('-'); | ||
297 | + if (dash >= 0) { | ||
298 | + String language = token.substring(0, dash); | ||
299 | + String country = token.substring(dash + 1); | ||
300 | + locale = new Locale(language, country); | ||
301 | + } else { | ||
302 | + locale = new Locale(token, ""); | ||
303 | + } | ||
304 | + headerLanguage = locale.getLanguage(); | ||
305 | + if (session != null) { | ||
306 | + session.locale = locale; | ||
307 | + session.put("language", headerLanguage); | ||
308 | + server.readTranslations(session, headerLanguage); | ||
309 | + } | ||
310 | + break; | ||
311 | + } | ||
312 | + } | ||
313 | + } | ||
314 | + } | ||
315 | + } else if (line.trim().length() == 0) { | ||
316 | + break; | ||
317 | + } | ||
318 | + } | ||
319 | + if (multipart) { | ||
320 | + // not supported | ||
321 | + } else if (session != null && len > 0) { | ||
322 | + byte[] bytes = DataUtils.newBytes(len); | ||
323 | + for (int pos = 0; pos < len;) { | ||
324 | + pos += input.read(bytes, pos, len - pos); | ||
325 | + } | ||
326 | + String s = new String(bytes); | ||
327 | + parseAttributes(s); | ||
328 | + } | ||
329 | + return keepAlive; | ||
330 | + } | ||
331 | + | ||
332 | + private static String getHeaderLineValue(String line) { | ||
333 | + return line.substring(line.indexOf(':') + 1).trim(); | ||
334 | + } | ||
335 | + | ||
336 | + @Override | ||
337 | + protected String adminShutdown() { | ||
338 | + stopNow(); | ||
339 | + return super.adminShutdown(); | ||
340 | + } | ||
341 | + | ||
342 | + private boolean allow() { | ||
343 | + if (server.getAllowOthers()) { | ||
344 | + return true; | ||
345 | + } | ||
346 | + try { | ||
347 | + return NetUtils.isLocalAddress(socket); | ||
348 | + } catch (UnknownHostException e) { | ||
349 | + server.traceError(e); | ||
350 | + return false; | ||
351 | + } | ||
352 | + } | ||
353 | + | ||
354 | + private void trace(String s) { | ||
355 | + server.trace(s); | ||
356 | + } | ||
357 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment