001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
006: * Contact: sequoia@continuent.org
007: *
008: * Licensed under the Apache License, Version 2.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: * Initial developer(s): Emmanuel Cecchet.
021: * Contributor(s): Mathieu Peltier, Nicolas Modrzyk.
022: */package org.continuent.sequoia.console.text.module;
023:
024: import java.sql.Connection;
025: import java.sql.DriverManager;
026: import java.sql.PreparedStatement;
027: import java.sql.ResultSet;
028: import java.sql.SQLException;
029: import java.sql.Savepoint;
030: import java.util.Hashtable;
031:
032: import org.continuent.sequoia.common.i18n.ConsoleTranslate;
033: import org.continuent.sequoia.common.util.Constants;
034: import org.continuent.sequoia.console.text.Console;
035: import org.continuent.sequoia.console.text.ConsoleException;
036: import org.continuent.sequoia.console.text.ConsoleLauncher;
037: import org.continuent.sequoia.console.text.commands.ConsoleCommand;
038: import org.continuent.sequoia.console.text.formatter.ResultSetFormatter;
039:
040: /**
041: * Sequoia Controller Virtual Database Console module.
042: *
043: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
044: * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
045: * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
046: * @version 1.0
047: */
048: public class VirtualDatabaseConsole extends AbstractConsoleModule {
049: private Connection connection = null;
050: /**
051: * contains a hash of <String, SavePoint> to handle savepoints (used by
052: * SetSavePoint and Rollback commands)
053: */
054: private Hashtable savePoints = new Hashtable();
055:
056: /** Default query timeout. */
057: private int timeout = 60;
058:
059: private int fetchsize = 0;
060:
061: private int maxrows = 0;
062:
063: private String login;
064: private String url;
065:
066: // for backwards compatiblity, multiline statement is disabled by default
067: private boolean multilineStatementsEnabled = false;
068: // request delimiter to use when multiline statement is enabled
069: private String delimiter = DEFAULT_REQUEST_DELIMITER;
070: private static final String DEFAULT_REQUEST_DELIMITER = ";";
071: // a buffer used when multiline statement is enabled to keep in
072: // memory the statement across each handlePrompt()
073: private StringBuffer currentRequest = new StringBuffer("");
074:
075: /**
076: * Creates a new <code>VirtualDatabaseAdmin</code> instance. Loads the
077: * driver
078: *
079: * @param console console console
080: */
081: public VirtualDatabaseConsole(Console console) {
082: super (console);
083: try {
084: Class.forName(System.getProperty("console.driver",
085: "org.continuent.sequoia.driver.Driver"));
086: } catch (Exception e) {
087: console.printError(ConsoleTranslate.get(
088: "sql.login.cannot.load.driver",
089: ConsoleLauncher.PRODUCT_NAME), e);
090: Runtime.getRuntime().exit(1);
091: }
092: if (console.isInteractive())
093: console.println(ConsoleTranslate.get(
094: "sql.login.loaded.driver", new String[] {
095: ConsoleLauncher.PRODUCT_NAME,
096: Constants.VERSION }));
097: }
098:
099: /**
100: * Get the JDBC connection used by the sql console.
101: *
102: * @return a JDBC connection
103: */
104: public Connection getConnection() {
105: return connection;
106: }
107:
108: /**
109: * Create a new connection from the driver.
110: *
111: * @param url the Sequoia url
112: * @param login the login to use to open the connection
113: * @param password the password to use to open the connection
114: * @return a new connection
115: * @throws ConsoleException if login failed
116: */
117: public Connection createConnection(String url, String login,
118: String password) throws ConsoleException {
119: try {
120: return DriverManager.getConnection(url, login, password);
121: } catch (Exception e) {
122: throw new ConsoleException(ConsoleTranslate.get(
123: "sql.login.connection.failed", new String[] { url,
124: e.getMessage() }));
125: }
126: }
127:
128: /**
129: * Executes a SQL statement.
130: *
131: * @param request the SQL request to execute
132: * @param displayResult <code>true</code> if the result must be printed on
133: * the standard output
134: */
135: public synchronized void execSQL(String request,
136: boolean displayResult) {
137: PreparedStatement stmt = null;
138: try {
139: stmt = connection.prepareStatement(request);
140: stmt.setQueryTimeout(timeout);
141: if (fetchsize != 0)
142: stmt.setFetchSize(fetchsize);
143: if (maxrows != 0)
144: stmt.setMaxRows(maxrows);
145:
146: long start = System.currentTimeMillis();
147: long end;
148: if (request.toLowerCase().matches("^(\\(|\\s+)*select.*")) {
149: ResultSet rs = stmt.executeQuery();
150: end = System.currentTimeMillis();
151: if (displayResult)
152: ResultSetFormatter.display(rs, fetchsize, console);
153: rs.close();
154: } else {
155: boolean hasResult = stmt.execute();
156: end = System.currentTimeMillis();
157:
158: int updatedRows;
159: do {
160: updatedRows = -1;
161: if (hasResult) {
162: ResultSet rs = stmt.getResultSet();
163: if (displayResult)
164: ResultSetFormatter.display(rs, fetchsize,
165: console);
166: rs.close();
167: } else {
168: updatedRows = stmt.getUpdateCount();
169: if (displayResult)
170: console.printInfo(ConsoleTranslate.get(
171: "sql.display.affected.rows",
172: updatedRows));
173: }
174: hasResult = stmt.getMoreResults();
175: updatedRows = stmt.getUpdateCount();
176: } while (hasResult || (updatedRows != -1));
177: }
178: console.printInfo(ConsoleTranslate.get(
179: "sql.display.query.time", new Long[] {
180: new Long((end - start) / 1000),
181: new Long((end - start) % 1000) }));
182: } catch (Exception e) {
183: console.printError(ConsoleTranslate.get(
184: "sql.command.sqlquery.error", e), e);
185: } finally {
186: try {
187: stmt.close();
188: } catch (Exception ignore) {
189: }
190: }
191: }
192:
193: /**
194: * Connects to a virtual database.
195: */
196: public void handlePrompt() {
197: while (!quit) {
198: try {
199: String cmd = console.readLine(this .getPromptString());
200: if (cmd == null) {
201: quit();
202: break;
203: }
204:
205: if (cmd.length() == 0)
206: continue;
207:
208: ConsoleCommand currentCommand = findConsoleCommand(cmd,
209: getHashCommands());
210:
211: if (currentCommand == null) {
212: if (multilineStatementsEnabled) {
213: // append a whitespace beacuse console.readLine() trim the cmd
214: currentRequest.append(" ").append(cmd);
215: if (currentRequest.toString().endsWith(
216: delimiter)) {
217: String request = removeDelimiterAndTrim(currentRequest
218: .toString());
219: execSQL(request, true);
220: currentRequest = new StringBuffer("");
221: }
222: } else {
223: execSQL(cmd, true);
224: }
225: } else {
226: currentCommand.execute(cmd.substring(
227: currentCommand.getCommandName().length())
228: .trim());
229: }
230: } catch (Exception e) {
231: console.printError(ConsoleTranslate.get(
232: "sql.display.exception", e), e);
233: if ((e instanceof RuntimeException)
234: || (!console.isInteractive() && console
235: .isExitOnError())) {
236: System.exit(1);
237: }
238: }
239: }
240: }
241:
242: /**
243: * Removes the delimiter at the end of the request and trims the resulting
244: * substring.
245: *
246: * @param request the SQL request
247: * @return a String representing the SQL request without its delimiter
248: * @see #delimiter
249: */
250: private String removeDelimiterAndTrim(String request) {
251: if (request == null) {
252: return "";
253: }
254: return request.substring(0,
255: request.length() - delimiter.length()).trim();
256: }
257:
258: /**
259: * @see org.continuent.sequoia.console.text.module.AbstractConsoleModule#getDescriptionString()
260: */
261: public String getDescriptionString() {
262: return "SQL Console";
263: }
264:
265: /**
266: * @see org.continuent.sequoia.console.text.module.AbstractConsoleModule#getModuleID()
267: */
268: protected String getModuleID() {
269: return "sql"; //$NON-NLS-1$
270: }
271:
272: /**
273: * @see org.continuent.sequoia.console.text.module.AbstractConsoleModule#getPromptString()
274: */
275: public String getPromptString() {
276: int ind1 = url.lastIndexOf('?');
277: int ind2 = url.lastIndexOf(';');
278: if (ind1 != -1 || ind2 != -1) {
279: String prompt;
280: prompt = (ind1 != -1) ? url.substring(0, ind1) : url;
281: prompt = (ind2 != -1) ? url.substring(0, ind2) : url;
282: return prompt + " (" + login + ")";
283: } else
284: return url + " (" + login + ")";
285: }
286:
287: /**
288: * Get a <code>SavePoint</code> identified by its <code>name</code>
289: *
290: * @param name name fo the <code>SavePoint</code>
291: * @return a <code>SavePoint</code> or <code>null</code> if no
292: * <code>SavePoint</code> with such a name has been previously added
293: */
294: public Savepoint getSavePoint(String name) {
295: return (Savepoint) savePoints.get(name);
296: }
297:
298: /**
299: * add a <code>SavePoint</code>
300: *
301: * @param savePoint the <code>SavePoint</code> to add
302: * @throws SQLException if the <code>savePoint</code> is unnamed
303: */
304: public void addSavePoint(Savepoint savePoint) throws SQLException {
305: savePoints.put(savePoint.getSavepointName(), savePoint);
306: }
307:
308: /**
309: * Get the timeout value (in seconds)
310: *
311: * @return the timeout value (in seconds)
312: */
313: public int getTimeout() {
314: return timeout;
315: }
316:
317: /**
318: * Set the timeout value (in seconds)
319: *
320: * @param timeout new timeout value (in seconds)
321: */
322: public void setTimeout(int timeout) {
323: this .timeout = timeout;
324: }
325:
326: /**
327: * Get the fetchsize value
328: *
329: * @return the fetchsize value
330: */
331: public int getFetchsize() {
332: return fetchsize;
333: }
334:
335: /**
336: * Set the fetchsize
337: *
338: * @param fetchsize new fetchsize value
339: */
340: public void setFetchsize(int fetchsize) {
341: this .fetchsize = fetchsize;
342: }
343:
344: /**
345: * Set the maxrows
346: *
347: * @param maxrows new maxrows value
348: */
349: public void setMaxrows(int maxrows) {
350: this .maxrows = maxrows;
351: }
352:
353: /**
354: * @see org.continuent.sequoia.console.text.module.AbstractConsoleModule#login(java.lang.String[])
355: */
356: public void login(String[] params) throws Exception {
357: quit = false;
358:
359: login = null;
360: url = (params.length > 0 && params[0] != null) ? params[0]
361: .trim() : null;
362: try {
363: if ((url == null) || url.trim().equals("")) {
364: url = console.readLine(ConsoleTranslate.get(
365: "sql.login.prompt.url",
366: ConsoleLauncher.PRODUCT_NAME));
367: if (url == null)
368: return;
369: }
370: login = console.readLine(ConsoleTranslate
371: .get("sql.login.prompt.user"));
372: if (login == null)
373: return;
374: String password = console.readPassword(ConsoleTranslate
375: .get("sql.login.prompt.password"));
376: if (password == null)
377: return;
378:
379: connection = createConnection(url, login, password);
380:
381: // Change console reader completor
382: console.getConsoleReader().removeCompletor(
383: console.getControllerModule().getCompletor());
384: console.getConsoleReader()
385: .addCompletor(this .getCompletor());
386: } catch (ConsoleException e) {
387: throw e;
388: } catch (Exception e) {
389: throw new ConsoleException(ConsoleTranslate.get(
390: "sql.login.exception", e));
391: }
392: }
393:
394: /**
395: * @see org.continuent.sequoia.console.text.module.AbstractConsoleModule#quit()
396: */
397: public void quit() {
398: if (connection != null) {
399: try {
400: connection.close();
401: } catch (Exception e) {
402: // ignore
403: }
404: }
405: super .quit();
406: }
407:
408: /**
409: * @see org.continuent.sequoia.console.text.module.AbstractConsoleModule#help()
410: */
411: public void help() {
412: super .help();
413: console.println();
414: console
415: .println(ConsoleTranslate
416: .get("sql.command.description"));
417: }
418:
419: /**
420: * Sets the request delimiter to use when multiline statement is enabled.
421: *
422: * @param delimiter the String to use as request delimiter
423: * @see #DEFAULT_REQUEST_DELIMITER
424: */
425: public void setRequestDelimiter(String delimiter) {
426: this .delimiter = delimiter;
427: }
428:
429: /**
430: * Returns the request delimiter
431: *
432: * @return a String representing the request delimiter
433: */
434: public String getRequestDelimiter() {
435: return delimiter;
436: }
437:
438: /**
439: * Enables multiline statements to be able to write one statement on several
440: * lines. A multiline statement is executed when the user inputs the request
441: * delimiter String at the end of a line. By default multiline statement is
442: * disabled (for backwards compatibility)
443: *
444: * @param multilineStatementsEnabled <code>true</code> if multiline
445: * statement must be enabled, <code>false</code> else
446: */
447: public void enableMultilineStatement(
448: boolean multilineStatementsEnabled) {
449: this.multilineStatementsEnabled = multilineStatementsEnabled;
450: }
451: }
|