diff src/main/java/org/monetdb/mcl/net/Target.java @ 791:4de810c22328 monetdbs

Refactor
author Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
date Fri, 01 Dec 2023 14:18:01 +0100 (17 months ago)
parents 547eca89fc5e
children 9dea0795a926
line wrap: on
line diff
--- a/src/main/java/org/monetdb/mcl/net/Target.java
+++ b/src/main/java/org/monetdb/mcl/net/Target.java
@@ -5,326 +5,570 @@ import java.util.Properties;
 import java.util.regex.Pattern;
 
 public class Target {
+    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 = 200;
+    private String hash = "";
+    private boolean debug = false;
+    private String logfile = "";
+
+    private boolean userWasSet = false;
+    private boolean passwordWasSet = false;
+    protected static final Target defaults = new Target();
+
     private static Pattern namePattern = Pattern.compile("^[a-zzA-Z_][-a-zA-Z0-9_.]*$");
     private static Pattern hashPattern = Pattern.compile("^sha256:[0-9a-fA-F:]*$");
-    private final boolean tls;
-    private final String host;
-    private final int port;
-    private final String database;
-    private final String tableschema;
-    private final String table;
-    private final String sock;
-    private final String sockdir;
-    private final String cert;
-    private final String certhash;
-    private final String clientkey;
-    private final String clientcert;
-    private final String user;
-    private final String password;
-    private final String language;
-    private final boolean autocommit;
-    private final String schema;
-    private final int timezone;
-    private final int binary;
-    private final int replysize;
-    private final String hash;
-    private final boolean debug;
-    private final String logfile;
 
-    public Target(Properties properties) throws ValidationError {
-
-        // 1. The parameters have the types listed in the table in [Section
-        //    Parameters](#parameters).
-        tls = validateBoolean(properties, Parameter.TLS);
-        host = validateString(properties, Parameter.HOST);
-        port = validateInt(properties, Parameter.PORT);
-        database = validateString(properties, Parameter.DATABASE);
-        tableschema = validateString(properties, Parameter.TABLESCHEMA);
-        table = validateString(properties, Parameter.TABLE);
-        sock = validateString(properties, Parameter.SOCK);
-        sockdir = validateString(properties, Parameter.SOCKDIR);
-        cert = validateString(properties, Parameter.CERT);
-        certhash = validateString(properties, Parameter.CERTHASH);
-        clientkey = validateString(properties, Parameter.CLIENTKEY);
-        clientcert = validateString(properties, Parameter.CLIENTCERT);
-        user = validateString(properties, Parameter.USER);
-        password = validateString(properties, Parameter.PASSWORD);
-        language = validateString(properties, Parameter.LANGUAGE);
-        autocommit = validateBoolean(properties, Parameter.AUTOCOMMIT);
-        schema = validateString(properties, Parameter.SCHEMA);
-        timezone = validateInt(properties, Parameter.TIMEZONE);
-        replysize = validateInt(properties, Parameter.REPLYSIZE);
-        hash = validateString(properties, Parameter.HASH);
-        debug = validateBoolean(properties, Parameter.DEBUG);
-        logfile = validateString(properties, Parameter.LOGFILE);
-
-        for (String name: properties.stringPropertyNames()) {
-            if (Parameter.forName(name) != null)
-                continue;
-            if (name.contains("_"))
-                continue;
-            throw new ValidationError("unknown parameter: " + name);
-        }
-
-        String binaryString = validateString(properties, Parameter.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");
-        binary = 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=");
+    public Target() {
+        this.timezone = (int)Parameter.TIMEZONE.getDefault();
     }
 
-    public static boolean validateBoolean(Properties props, Parameter parm) throws ValidationError {
-        Object value = props.get(parm.name);
-        if (value != null) {
-            return (Boolean) parm.type.parse(parm.name, (String) value);
-        } else {
-            return (Boolean) getDefault(parm);
+    public void barrier() {
+        if (userWasSet && !passwordWasSet)
+            password = "";
+        userWasSet = false;
+        passwordWasSet = false;
+    }
+
+    public static String packHost(String host) {
+        switch (host) {
+            case "localhost":
+                return "localhost.";
+            case "":
+                return "localhost";
+            default:
+                return host;
         }
     }
 
-    public static int validateInt(Properties props, Parameter parm) throws ValidationError {
-        Object value = props.get(parm.name);
-        if (value != null) {
-            return (Integer) parm.type.parse(parm.name, (String) value);
-        } else {
-            return (Integer) getDefault(parm);
-        }
+    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 static String validateString(Properties props, Parameter parm) throws ValidationError {
-        Object value = props.get(parm.name);
-        if (value != null) {
-            return (String) parm.type.parse(parm.name, (String) value);
-        } else {
-            return (String) getDefault(parm);
+    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;
+            default:
+                throw new IllegalStateException("unreachable -- missing case");
         }
     }
 
-    private static int timezone() {
-        Calendar cal = Calendar.getInstance();
-        int offsetMillis = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
-        int offsetSeconds = offsetMillis / 1000;
-        return offsetSeconds;
+    public String getString(Parameter parm) {
+        Object value = getObject(parm);
+        return parm.type.format(value);
     }
 
-    public static Object getDefault(Parameter parm) {
-        if (parm == Parameter.TIMEZONE) return timezone();
-        else return parm.defaultValue;
+    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;
+            default:
+                throw new IllegalStateException("unreachable -- missing case");
+        }
     }
 
-    public static Properties defaultProperties() {
-        Properties props = new Properties();
-        return props;
+    public static String unpackHost(String host) {
+        switch (host) {
+            case "localhost.":
+                return "localhost";
+            case "localhost":
+                return "";
+            default:
+                return host;
+        }
     }
 
-    public boolean getTls() {
+    public boolean isTls() {
         return tls;
     }
 
-    // Getter is private because you probably want connectTcp() instead
-    private String getHost() {
+    public void setTls(boolean tls) {
+        this.tls = tls;
+    }
+
+    public String getHost() {
         return host;
     }
 
-    // Getter is private because you probably want connectPort() instead
-    private int getPort() {
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public int getPort() {
         return port;
     }
 
+    public void setPort(int port) {
+        this.port = port;
+    }
+
     public String getDatabase() {
         return database;
     }
 
+    public void setDatabase(String database) {
+        this.database = database;
+    }
+
     public String getTableschema() {
         return tableschema;
     }
 
+    public void setTableschema(String tableschema) {
+        this.tableschema = tableschema;
+    }
+
     public String getTable() {
         return table;
     }
 
-    // Getter is private because you probably want connectUnix() instead
-    private String getSock() {
+    public void setTable(String table) {
+        this.table = table;
+    }
+
+    public String getSock() {
         return sock;
     }
 
+    public void setSock(String sock) {
+        this.sock = sock;
+    }
+
     public String getSockdir() {
         return sockdir;
     }
 
+    public void setSockdir(String sockdir) {
+        this.sockdir = sockdir;
+    }
+
     public String getCert() {
         return cert;
     }
 
+    public void setCert(String cert) {
+        this.cert = cert;
+    }
+
     public String getCerthash() {
         return certhash;
     }
 
+    public void setCerthash(String certhash) {
+        this.certhash = certhash;
+    }
+
     public String getClientkey() {
         return clientkey;
     }
 
+    public void setClientkey(String clientkey) {
+        this.clientkey = clientkey;
+    }
+
     public String getClientcert() {
         return clientcert;
     }
 
+    public void setClientcert(String clientcert) {
+        this.clientcert = clientcert;
+    }
+
     public String getUser() {
         return user;
     }
 
+    public void setUser(String user) {
+        this.user = user;
+        this.userWasSet = true;
+    }
+
     public String getPassword() {
         return password;
     }
 
+    public void setPassword(String password) {
+        this.password = password;
+        this.passwordWasSet = true;
+    }
+
     public String getLanguage() {
         return language;
     }
 
-    public boolean getAutocommit() {
+    public void setLanguage(String language) {
+        this.language = language;
+    }
+
+    public boolean isAutocommit() {
         return autocommit;
     }
 
+    public void setAutocommit(boolean autocommit) {
+        this.autocommit = autocommit;
+    }
+
     public String getSchema() {
         return schema;
     }
 
+    public void setSchema(String schema) {
+        this.schema = schema;
+    }
+
     public int getTimezone() {
         return timezone;
     }
 
-    // Getter is private because you probably want connectBinary() instead
-    public int getBinary() {
+    public void setTimezone(int timezone) {
+        this.timezone = timezone;
+    }
+
+    public String getBinary() {
         return binary;
     }
 
+    public void setBinary(String binary) {
+        this.binary = binary;
+    }
+
     public int getReplysize() {
         return replysize;
     }
 
+    public void setReplysize(int replysize) {
+        this.replysize = replysize;
+    }
+
     public String getHash() {
         return hash;
     }
 
-    public boolean getDebug() {
+    public void setHash(String hash) {
+        this.hash = hash;
+    }
+
+    public boolean isDebug() {
         return debug;
     }
 
+    public void setDebug(boolean debug) {
+        this.debug = debug;
+    }
+
     public String getLogfile() {
         return logfile;
     }
 
-    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 void setLogfile(String logfile) {
+        this.logfile = logfile;
     }
 
-    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 Validated validate() throws ValidationError {
+        return new Validated();
     }
 
-    public Verify connectVerify() {
-        if (!tls) return Verify.None;
-        if (!certhash.isEmpty()) return Verify.Hash;
-        if (!cert.isEmpty()) return Verify.Cert;
-        return Verify.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://");
+            }
 
-    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));
+            // 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=");
+        }
+
+        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;
         }
-        return builder.toString();
-    }
+
+        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 int connectBinary() {
-        return binary;
-    }
+        public String getLanguage() {
+            return language;
+        }
+
+        public boolean getAutocommit() {
+            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 String connectClientKey() {
-        return clientkey;
-    }
+        public boolean getDebug() {
+            return debug;
+        }
+
+        public String getLogfile() {
+            return logfile;
+        }
+
+        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 connectClientCert() {
-        return clientcert.isEmpty() ? clientkey : clientcert;
+        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;
+        }
     }
 }