Mercurial > hg > monetdb-java
view src/main/java/org/monetdb/mcl/net/Target.java @ 940:c5e47b8a509c
Prefer getters of class Target.Validated over class Target
This makes it easier to check that all settings are used.
author | Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com> |
---|---|
date | Wed, 08 Jan 2025 16:30:25 +0100 (3 months ago) |
parents | d416e9b6b3d0 |
children |
line wrap: on
line source
/* * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2024, 2025 MonetDB Foundation; * Copyright August 2008 - 2023 MonetDB B.V.; * Copyright 1997 - July 2008 CWI. */ package org.monetdb.mcl.net; import java.net.URISyntaxException; import java.util.Properties; import java.util.regex.Pattern; public final class Target { protected static final Target defaults = new Target(); private static final Pattern namePattern = Pattern.compile("^[a-zzA-Z_][-a-zA-Z0-9_.]*$"); private static final Pattern hashPattern = Pattern.compile("^sha256:[0-9a-fA-F:]*$"); private boolean tls = false; private String host = ""; private int port = -1; private String database = ""; private String tableschema = ""; private String table = ""; private String sock = ""; private String sockdir = "/tmp"; private String cert = ""; private String certhash = ""; private String clientkey = ""; private String clientcert = ""; private String user = ""; private String password = ""; private String language = "sql"; private boolean autocommit = true; private String schema = ""; private int timezone; private String binary = "on"; private int replySize = 250; private String hash = ""; private boolean debug = false; private String logfile = ""; private int soTimeout = 0; private boolean treatClobAsVarchar = true; private boolean treatBlobAsBinary = true; private boolean clientInfo = true; private String clientApplication = ""; private String clientRemark = ""; private boolean userWasSet = false; private boolean passwordWasSet = false; private Validated validated = null; public Target() { this.timezone = (int) Parameter.TIMEZONE.getDefault(); } public Target(String url, Properties props) throws URISyntaxException, ValidationError { this(); setProperties(props); parseUrl(url); } public static String packHost(String host) { switch (host) { case "localhost": return "localhost."; case "": return "localhost"; default: return host; } } public static String unpackHost(String host) { switch (host) { case "localhost.": return "localhost"; case "localhost": return ""; default: return host; } } public void barrier() { if (userWasSet && !passwordWasSet) password = ""; userWasSet = false; passwordWasSet = false; } public void setString(String key, String value) throws ValidationError { Parameter parm = Parameter.forName(key); if (parm != null) setString(parm, value); else if (!Parameter.isIgnored(key)) throw new ValidationError(key, "unknown parameter"); } public void setString(Parameter parm, String value) throws ValidationError { if (value == null) throw new NullPointerException("'value' must not be null"); assign(parm, parm.type.parse(parm.name, value)); } public void clear(Parameter parm) { assign(parm, parm.getDefault()); } public void parseUrl(String url) throws URISyntaxException, ValidationError { if (url == null) return; if (url.startsWith("jdbc:")) url = url.substring(5); if (url.equals("monetdb:")) { return; } MonetUrlParser.parse(this, url); } private void assign(Parameter parm, Object value) { switch (parm) { case TLS: setTls((boolean) value); break; case HOST: setHost((String) value); break; case PORT: setPort((int) value); break; case DATABASE: setDatabase((String) value); break; case TABLESCHEMA: setTableschema((String) value); break; case TABLE: setTable((String) value); break; case SOCK: setSock((String) value); break; case SOCKDIR: setSockdir((String) value); break; case CERT: setCert((String) value); break; case CERTHASH: setCerthash((String) value); break; case CLIENTKEY: setClientkey((String) value); break; case CLIENTCERT: setClientcert((String) value); break; case USER: setUser((String) value); break; case PASSWORD: setPassword((String) value); break; case LANGUAGE: setLanguage((String) value); break; case AUTOCOMMIT: setAutocommit((boolean) value); break; case SCHEMA: setSchema((String) value); break; case TIMEZONE: setTimezone((int) value); break; case BINARY: setBinary((String) value); break; case REPLYSIZE: setReplySize((int) value); break; case FETCHSIZE: setReplySize((int) value); break; case HASH: setHash((String) value); break; case DEBUG: setDebug((boolean) value); break; case LOGFILE: setLogfile((String) value); break; case SO_TIMEOUT: setSoTimeout((int) value); break; case CLOB_AS_VARCHAR: setTreatClobAsVarchar((boolean) value); break; case BLOB_AS_BINARY: setTreatBlobAsBinary((boolean) value); break; case CLIENT_INFO: setClientInfo((boolean) value); break; case CLIENT_APPLICATION: setClientApplication((String) value); break; case CLIENT_REMARK: setClientRemark((String) value); break; default: throw new IllegalStateException("unreachable -- missing case: " + parm.name); } } public String getString(Parameter parm) { Object value = getObject(parm); return parm.type.format(value); } public Object getObject(Parameter parm) { switch (parm) { case TLS: return tls; case HOST: return host; case PORT: return port; case DATABASE: return database; case TABLESCHEMA: return tableschema; case TABLE: return table; case SOCK: return sock; case SOCKDIR: return sockdir; case CERT: return cert; case CERTHASH: return certhash; case CLIENTKEY: return clientkey; case CLIENTCERT: return clientcert; case USER: return user; case PASSWORD: return password; case LANGUAGE: return language; case AUTOCOMMIT: return autocommit; case SCHEMA: return schema; case TIMEZONE: return timezone; case BINARY: return binary; case REPLYSIZE: return replySize; case FETCHSIZE: return replySize; case HASH: return hash; case DEBUG: return debug; case LOGFILE: return logfile; case SO_TIMEOUT: return soTimeout; case CLOB_AS_VARCHAR: return treatClobAsVarchar; case BLOB_AS_BINARY: return treatBlobAsBinary; case CLIENT_INFO: return clientInfo; case CLIENT_APPLICATION: return clientApplication; case CLIENT_REMARK: return clientRemark; default: throw new IllegalStateException("unreachable -- missing case"); } } public boolean isTls() { return tls; } public void setTls(boolean tls) { this.tls = tls; validated = null; } public String getHost() { return host; } public void setHost(String host) { this.host = host; validated = null; } public int getPort() { return port; } public void setPort(int port) { this.port = port; validated = null; } public String getDatabase() { return database; } public void setDatabase(String database) { this.database = database; validated = null; } public String getTableschema() { return tableschema; } public void setTableschema(String tableschema) { this.tableschema = tableschema; validated = null; } public String getTable() { return table; } public void setTable(String table) { this.table = table; validated = null; } public String getSock() { return sock; } public void setSock(String sock) { this.sock = sock; validated = null; } public String getSockdir() { return sockdir; } public void setSockdir(String sockdir) { this.sockdir = sockdir; validated = null; } public String getCert() { return cert; } public void setCert(String cert) { this.cert = cert; validated = null; } public String getCerthash() { return certhash; } public void setCerthash(String certhash) { this.certhash = certhash; validated = null; } public String getClientkey() { return clientkey; } public void setClientkey(String clientkey) { this.clientkey = clientkey; validated = null; } public String getClientcert() { return clientcert; } public void setClientcert(String clientcert) { this.clientcert = clientcert; validated = null; } public String getUser() { return user; } public void setUser(String user) { this.user = user; this.userWasSet = true; validated = null; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; this.passwordWasSet = true; validated = null; } public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; validated = null; } public boolean isAutocommit() { return autocommit; } public void setAutocommit(boolean autocommit) { this.autocommit = autocommit; validated = null; } public String getSchema() { return schema; } public void setSchema(String schema) { this.schema = schema; validated = null; } public int getTimezone() { return timezone; } public void setTimezone(int timezone) { this.timezone = timezone; validated = null; } public String getBinary() { return binary; } public void setBinary(String binary) { this.binary = binary; validated = null; } public int getReplySize() { return replySize; } public void setReplySize(int replySize) { this.replySize = replySize; validated = null; } public String getHash() { return hash; } public void setHash(String hash) { this.hash = hash; validated = null; } public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; validated = null; } public String getLogfile() { return logfile; } public void setLogfile(String logfile) { this.logfile = logfile; validated = null; } public int getSoTimeout() { return soTimeout; } public void setSoTimeout(int soTimeout) { this.soTimeout = soTimeout; validated = null; } public boolean isTreatClobAsVarchar() { return treatClobAsVarchar; } public void setTreatClobAsVarchar(boolean treatClobAsVarchar) { this.treatClobAsVarchar = treatClobAsVarchar; validated = null; } public boolean isTreatBlobAsBinary() { return treatBlobAsBinary; } public void setTreatBlobAsBinary(boolean treatBlobAsBinary) { this.treatBlobAsBinary = treatBlobAsBinary; validated = null; } public boolean sendClientInfo() { return clientInfo; } public void setClientInfo(boolean clientInfo) { this.clientInfo = clientInfo; } public String getClientApplication() { return clientApplication; } public void setClientApplication(String clientApplication) { this.clientApplication = clientApplication; } public String getClientRemark() { return clientRemark; } public void setClientRemark(String clientRemark) { this.clientRemark = clientRemark; } public Validated validate() throws ValidationError { if (validated == null) validated = new Validated(); return validated; } public String buildUrl() { final StringBuilder sb = new StringBuilder(128); sb.append("jdbc:"); sb.append(tls ? "monetdbs" : "monetdb"); sb.append("://"); sb.append(packHost(host)); if (!Parameter.PORT.getDefault().equals(port)) { sb.append(':'); sb.append(port); } sb.append('/').append(database); String sep = "?"; for (Parameter parm : Parameter.values()) { if (parm.isCore || parm == Parameter.USER || parm == Parameter.PASSWORD) continue; Object defaultValue = parm.getDefault(); if (defaultValue == null) continue; Object value = getObject(parm); if (value.equals(defaultValue)) continue; sb.append(sep).append(parm.name).append('='); String raw = getString(parm); String encoded = MonetUrlParser.percentEncode(raw); sb.append(encoded); sep = "&"; } return sb.toString(); } public Properties getProperties() { Properties props = new Properties(); for (Parameter parm : Parameter.values()) { Object defaultValue = parm.getDefault(); if (defaultValue == null || defaultValue.equals(getObject(parm))) continue; String value = getString(parm); if (parm == Parameter.HOST) value = packHost(host); props.setProperty(parm.name, value); } return props; } public void setProperties(Properties props) throws ValidationError { if (props != null) { for (String key : props.stringPropertyNames()) { String value = props.getProperty(key); if (key.equals(Parameter.HOST.name)) value = Target.unpackHost(value); setString(key, value); } } } public enum Verify { None, Cert, Hash, System } public class Validated { private final int nbinary; Validated() throws ValidationError { // 1. The parameters have the types listed in the table in [Section // Parameters](#parameters). String binaryString = binary; int binaryInt; try { binaryInt = (int) ParameterType.Int.parse(Parameter.BINARY.name, binaryString); } catch (ValidationError e) { try { boolean b = (boolean) ParameterType.Bool.parse(Parameter.BINARY.name, binaryString); binaryInt = b ? 65535 : 0; } catch (ValidationError ee) { throw new ValidationError("binary= must be either a number or true/yes/on/false/no/off"); } } if (binaryInt < 0) throw new ValidationError("binary= cannot be negative"); nbinary = binaryInt; // 2. At least one of **sock** and **host** must be empty. if (!sock.isEmpty() && !host.isEmpty()) throw new ValidationError("sock=" + sock + " cannot be combined with host=" + host); // 3. The string parameter **binary** must either parse as a boolean or as a // non-negative integer. // // (checked above) // 4. If **sock** is not empty, **tls** must be 'off'. if (!sock.isEmpty() && tls) throw new ValidationError("monetdbs:// cannot be combined with sock="); // 5. If **certhash** is not empty, it must be of the form `{sha256}hexdigits` // where hexdigits is a non-empty sequence of 0-9, a-f, A-F and colons. // TODO if (!certhash.isEmpty()) { if (!certhash.toLowerCase().startsWith("sha256:")) throw new ValidationError("certificate hash must start with 'sha256:'"); if (!hashPattern.matcher(certhash).matches()) throw new ValidationError("invalid certificate hash"); } // 6. If **tls** is 'off', **cert** and **certhash** must be 'off' as well. if (!tls) { if (!cert.isEmpty() || !certhash.isEmpty()) throw new ValidationError("cert= and certhash= are only allowed in combination with monetdbs://"); } // 7. Parameters **database**, **tableschema** and **table** must consist only of // upper- and lowercase letters, digits, periods, dashes and underscores. They must not // start with a dash. // If **table** is not empty, **tableschema** must also not be empty. // If **tableschema** is not empty, **database** must also not be empty. if (database.isEmpty() && !tableschema.isEmpty()) throw new ValidationError("table schema cannot be set without database"); if (tableschema.isEmpty() && !table.isEmpty()) throw new ValidationError("table cannot be set without schema"); if (!database.isEmpty() && !namePattern.matcher(database).matches()) throw new ValidationError("invalid database name"); if (!tableschema.isEmpty() && !namePattern.matcher(tableschema).matches()) throw new ValidationError("invalid table schema name"); if (!table.isEmpty() && !namePattern.matcher(table).matches()) throw new ValidationError("invalid table name"); // 8. Parameter **port** must be -1 or in the range 1-65535. if (port < -1 || port == 0 || port > 65535) throw new ValidationError("invalid port number " + port); // 9. If **clientcert** is set, **clientkey** must also be set. if (!clientcert.isEmpty() && clientkey.isEmpty()) throw new ValidationError("clientcert= is only valid in combination with clientkey="); // JDBC specific if (soTimeout < 0) throw new ValidationError("so_timeout= must not be negative"); } public boolean getTls() { return tls; } // Getter is private because you probably want connectTcp() instead private String getHost() { return host; } // Getter is private because you probably want connectPort() instead private int getPort() { return port; } public String getDatabase() { return database; } public String getTableschema() { return tableschema; } public String getTable() { return table; } // Getter is private because you probably want connectUnix() instead private String getSock() { return sock; } public String getSockdir() { return sockdir; } public String getCert() { return cert; } public String getCerthash() { return certhash; } public String getClientkey() { return clientkey; } public String getClientcert() { return clientcert; } public String getUser() { return user; } public String getPassword() { return password; } public String getLanguage() { return language; } public boolean isAutocommit() { return autocommit; } public String getSchema() { return schema; } public int getTimezone() { return timezone; } // Getter is private because you probably want connectBinary() instead public int getBinary() { return nbinary; } public int getReplySize() { return replySize; } public String getHash() { return hash; } public boolean isDebug() { return debug; } public String getLogfile() { return logfile; } public int getSoTimeout() { return soTimeout; } public boolean isTreatClobAsVarchar() { return treatClobAsVarchar; } public boolean isTreatBlobAsBinary() { return treatBlobAsBinary; } public boolean sendClientInfo() { return clientInfo; } public String getClientApplication() { return clientApplication; } public String getClientRemark() { return clientRemark; } public boolean connectScan() { if (database.isEmpty()) return false; if (!sock.isEmpty() || !host.isEmpty() || port != -1) return false; return !tls; } public int connectPort() { return port == -1 ? 50000 : port; } public String connectUnix() { if (!sock.isEmpty()) return sock; if (tls) return ""; if (host.isEmpty()) return sockdir + "/.s.monetdb." + connectPort(); return ""; } public String connectTcp() { if (!sock.isEmpty()) return ""; if (host.isEmpty()) return "localhost"; return host; } public Verify connectVerify() { if (!tls) return Verify.None; if (!certhash.isEmpty()) return Verify.Hash; if (!cert.isEmpty()) return Verify.Cert; return Verify.System; } public String connectCertHashDigits() { if (!tls) return null; StringBuilder builder = new StringBuilder(certhash.length()); for (int i = "sha256:".length(); i < certhash.length(); i++) { char c = certhash.charAt(i); if (Character.digit(c, 16) >= 0) builder.append(Character.toLowerCase(c)); } return builder.toString(); } public int connectBinary() { return nbinary; } public String connectClientKey() { return clientkey; } public String connectClientCert() { return clientcert.isEmpty() ? clientkey : clientcert; } } }