📄 monetconnection.java
字号:
/* * The contents of this file are subject to the MonetDB Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://monetdb.cwi.nl/Legal/MonetDBLicense-1.1.html * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * The Original Code is the MonetDB Database System. * * The Initial Developer of the Original Code is CWI. * Portions created by CWI are Copyright (C) 1997-2007 CWI. * All Rights Reserved. */package nl.cwi.monetdb.jdbc;import java.sql.*;import java.util.*;import java.io.*;import java.nio.*;import java.security.*;import nl.cwi.monetdb.jdbc.util.*;/** * A Connection suitable for the MonetDB database. * <br /><br /> * This connection represents a connection (session) to a MonetDB * database. SQL statements are executed and results are returned within * the context of a connection. This Connection object holds a physical * connection to the MonetDB database. * <br /><br /> * A Connection object's database should able to provide information * describing its tables, its supported SQL grammar, its stored * procedures, the capabilities of this connection, and so on. This * information is obtained with the getMetaData method.<br /> * Note: By default a Connection object is in auto-commit mode, which * means that it automatically commits changes after executing each * statement. If auto-commit mode has been disabled, the method commit * must be called explicitly in order to commit changes; otherwise, * database changes will not be saved. * <br /><br /> * The current state of this connection is that it nearly implements the * whole Connection interface.<br /> * Additionally, the static method getEmbeddedInstanceConnection() * provides a Connection for embedded situations, where an embedded * Mserver is started and used. * * @author Fabian Groffen <Fabian.Groffen@cwi.nl> * @version 1.2 */public class MonetConnection implements Connection { /** The hostname to connect to */ private final String hostname; /** The port to connect on the host to */ private final int port; /** The database to use (currently not used) */ private final String database; /** The username to use when authenticating */ private final String username; /** The password to use when authenticating */ private final String password; /** A connection to Mserver using a TCP socket */ private final MonetSocketBlockMode monet; /** Whether this Connection is closed (and cannot be used anymore) */ private boolean closed; /** Whether this Connection is in autocommit mode */ private boolean autoCommit = true; /** The stack of warnings for this Connection object */ private SQLWarning warnings = null; /** The Connection specific mapping of user defined types to Java * types (not used) */ private Map typeMap = new HashMap(); // See javadoc for documentation about WeakHashMap if you don't know what // it does !!!NOW!!! (only when you deal with it of course) /** A Map containing all (active) Statements created from this Connection */ private Map statements = new WeakHashMap(); /** The number of results we receive from the server at once */ private int curReplySize = -1; // the server by default uses -1 (all) /** A template to apply to each query (like pre and post fixes) */ String[] queryTempl; /** A template to apply to each command (like pre and post fixes) */ String[] commandTempl; /** the SQL language */ final static int LANG_SQL = 0; /** the XQuery language */ final static int LANG_XQUERY = 1; /** the MIL language (officially *NOT* supported) */ final static int LANG_MIL = 2; /** an unknown language */ final static int LANG_UNKNOWN = -1; /** The language which is used */ final int lang; /** Query types (copied from sql_query.mx) */ final static int Q_PARSE = '0'; final static int Q_TABLE = '1'; final static int Q_UPDATE = '2'; final static int Q_SCHEMA = '3'; final static int Q_TRANS = '4'; final static int Q_PREPARE = '5'; final static int Q_BLOCK = '6'; /** Embedded instance */ private static MonetEmbeddedInstance embeddedInstance = null; /** Embedded properties */ private static Properties embeddedProps = null; /** * Constructor of a Connection for MonetDB. At this moment the * current implementation limits itself to storing the given host, * database, username and password for later use by the * createStatement() call. This constructor is only accessible to * classes from the jdbc package. * * @param props a Property hashtable holding the properties needed for * connecting * @throws SQLException if a database error occurs * @throws IllegalArgumentException is one of the arguments is null or empty */ MonetConnection( Properties props) throws SQLException, IllegalArgumentException, MonetRedirectException { this.hostname = props.getProperty("host"); int port; try { port = Integer.parseInt(props.getProperty("port")); } catch (NumberFormatException e) { port = 0; } this.port = port; this.database = props.getProperty("database"); this.username = props.getProperty("user"); this.password = props.getProperty("password"); String language = props.getProperty("language"); boolean debug = Boolean.valueOf(props.getProperty("debug")).booleanValue(); String hash = props.getProperty("hash"); // check input arguments if (hostname == null || hostname.trim().equals("")) throw new IllegalArgumentException("hostname should not be null or empty"); if (port == 0) throw new IllegalArgumentException("port should not be 0"); if (database == null || database.trim().equals("")) throw new IllegalArgumentException("database should not be null or empty"); if (username == null || username.trim().equals("")) throw new IllegalArgumentException("user should not be null or empty"); if (password == null || password.trim().equals("")) throw new IllegalArgumentException("password should not be null or empty"); if (language == null || language.trim().equals("")) { language = "sql"; addWarning("No language given, defaulting to 'sql'"); } // initialise query templates (filled later, but needed below) queryTempl = new String[3]; // pre, post, sep commandTempl = new String[3]; // pre, post, sep try { monet = new MonetSocketBlockMode(hostname, port); /* * There is no need for a lock on the monet object here. * Since we just created the object, and the reference to * this object has not yet been returned to the caller, * noone can (in a legal way) know about the object. */ // we're debugging here... uhm, should be off in real life if (debug) { try { String fname = props.getProperty("logfile", "monet_" + System.currentTimeMillis() + ".log"); File f = new File(fname); int ext = fname.lastIndexOf("."); if (ext < 0) ext = fname.length(); String pre = fname.substring(0, ext); String suf = fname.substring(ext); for (int i = 1; f.exists(); i++) { f = new File(pre + "-" + i + suf); } monet.debug(f.getAbsolutePath()); } catch (IOException ex) { throw new SQLException("Opening logfile failed: " + ex.getMessage()); } } // read challenge, send response String[] nulltempl = {null, null, null}; monet.writeLine(nulltempl, getChallengeResponse( monet, monet.readLine(), username, password, language, database, hash ) ); // read monet response till prompt List redirects = null; String err = "", tmp; int lineType = 0; while (lineType != MonetSocketBlockMode.PROMPT1) { if ((tmp = monet.readLine()) == null) throw new IOException("Connection to server lost!"); if ((lineType = monet.getLineType()) == MonetSocketBlockMode.ERROR) { err += "\n" + tmp.substring(1); } else if (lineType == MonetSocketBlockMode.INFO) { addWarning(tmp.substring(1)); } else if (lineType == MonetSocketBlockMode.REDIRECT) { if (redirects == null) redirects = new ArrayList(); redirects.add(tmp.substring(1)); } } if (err != "") { monet.disconnect(); throw new SQLException(err.trim()); } if (redirects != null) { monet.disconnect(); throw new MonetRedirectException(redirects); } // we seem to have managed to log in, let's store the // language used if ("sql".equals(language)) { lang = LANG_SQL; } else if ("xquery".equals(language)) { lang = LANG_XQUERY; } else if ("mil".equals(language)) { lang = LANG_MIL; } else { lang = LANG_UNKNOWN; } // we're ready for commands! } catch (IOException e) { throw new SQLException("Unable to connect (" + hostname + ":" + port + "): " + e.getMessage()); } // fill the query templates if (lang == LANG_SQL) { queryTempl[0] = "s"; // pre queryTempl[1] = ";"; // post queryTempl[2] = ";\n"; // separator commandTempl[0] = "X"; // pre commandTempl[1] = null; // post commandTempl[2] = "\nX"; // separator } else if (lang == LANG_XQUERY) { queryTempl[0] = "s"; queryTempl[1] = null; queryTempl[2] = ","; commandTempl[0] = "X"; // pre commandTempl[1] = null; // post commandTempl[2] = "\nX"; // separator } else if (lang == LANG_MIL) { queryTempl[0] = null; queryTempl[1] = ";"; queryTempl[2] = ";\n"; commandTempl[0] = null; // pre commandTempl[1] = null; // post commandTempl[2] = null; // separator } // the following initialisers are only valid when the language // is SQL... if (lang == LANG_SQL) { // enable auto commit setAutoCommit(true); // set our time zone on the server Calendar cal = Calendar.getInstance(); int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (60 * 1000); String tz = offset < 0 ? "-" : "+"; tz += (Math.abs(offset) / 60 < 10 ? "0" : "") + (Math.abs(offset) / 60) + ":"; offset -= (offset / 60) * 60; tz += (offset < 10 ? "0" : "") + offset; sendIndependantCommand("SET TIME ZONE INTERVAL '" + tz + "' HOUR TO MINUTE"); } // the following initialisers are only valid when the language // is XQUERY... if (lang == LANG_XQUERY) { // output format sendControlCommand("output seq"); } // we're absolutely not closed, since we're brand new closed = false; } /** * A little helper function that processes a challenge string, and * returns a response string for the server. If the challenge * string is null, a challengeless response is returned. * * @param monet the MonetSocketBlockMode stream to write to * @param chalstr the challenge string * @param username the username to use * @param password the password to use * @param language the language to use * @param database the database to connect to * @param hash the hash method(s) to use, or NULL for all supported * hashes */ private String getChallengeResponse( MonetSocketBlockMode monet, String chalstr, String username, String password, String language, String database, String hash ) throws SQLException, IOException { int version = 0; String response; // hack alert monet.readLine(); /* prompt */ // parse the challenge string, split it on ':' String[] chaltok = chalstr.split(":"); if (chaltok.length < 4) throw new SQLException("Server challenge string unusable!"); // challenge string to use as salt/key String challenge = chaltok[0]; // chaltok[1]; // server type, not needed yet try { version = Integer.parseInt(chaltok[2].trim()); // protocol version } catch (NumberFormatException e) { throw new SQLException("Protocol version unparseable: " + chaltok[3]); } // handle the challenge according to the version it is switch (version) { default: throw new SQLException("Unsupported protocol version: " + version); case 8: // proto 7 (finally) used the challenge and works with a // password hash. The supported implementations come // from the server challenge. We chose the best hash // we can find, in the order SHA1, MD5, plain. Also, // the byte-order is reported in the challenge string, // which makes sense, since only blockmode is supported. // proto 8 made this obsolete, but retained the // byteorder report for future "binary" transports. In // proto 8, the byteorder of the blocks is always little // endian because most machines today are. String hashes = (hash == null ? chaltok[3] : hash); String pwhash; if (hashes.indexOf("SHA1") != -1) { try { MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(password.getBytes("UTF-8")); md.update(challenge.getBytes("UTF-8")); byte[] digest = md.digest(); pwhash = "{SHA1}" + toHex(digest); } catch (NoSuchAlgorithmException e) { throw new AssertionError("internal error: " + e.toString()); } catch (UnsupportedEncodingException e) { throw new AssertionError("internal error: " + e.toString()); } } else if (hashes.indexOf("MD5") != -1) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(password.getBytes("UTF-8")); md.update(challenge.getBytes("UTF-8")); byte[] digest = md.digest(); pwhash = "{MD5}" + toHex(digest); } catch (NoSuchAlgorithmException e) { throw new AssertionError("internal error: " + e.toString()); } catch (UnsupportedEncodingException e) { throw new AssertionError("internal error: " + e.toString()); } } else if (hashes.indexOf("plain") != -1) { pwhash = "{plain}" + password + challenge; } else { throw new SQLException("no supported password hashes in " + hashes); } // TODO: some day when we need this, we should store // this if (chaltok[4].equals("BIG")) { // byte-order of server is big-endian } else if (chaltok[4].equals("LIT")) { // byte-order of server is little-endian } else { throw new SQLException("Invalid byte-order: " + chaltok[5]); } // generate response response = "BIG:"; // JVM byte-order is big-endian response += username + ":" + pwhash + ":" + language; response += ":" + database + ":"; return(response);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -