content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to you under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.calcite.avatica;
  
  import org.apache.calcite.avatica.ha.LBStrategy;
  import org.apache.calcite.avatica.remote.AvaticaHttpClientFactory;
  import org.apache.calcite.avatica.remote.Service;
  
  import java.io.File;
  import java.math.BigDecimal;
  import java.util.LinkedHashMap;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Properties;
  
  /** Implementation of {@link ConnectionConfig}. */
  public class ConnectionConfigImpl implements ConnectionConfig {
  
    protected final Properties properties;
  
    public ConnectionConfigImpl(Properties properties) {
      this.properties = properties;
    }
  
    public String schema() {
      return BuiltInConnectionProperty.SCHEMA.wrap(properties).getString();
    }
  
    public String timeZone() {
      return BuiltInConnectionProperty.TIME_ZONE.wrap(properties).getString();
    }
  
    public Service.Factory factory() {
      return BuiltInConnectionProperty.FACTORY.wrap(properties)
          .getPlugin(Service.Factory.class, null);
    }
  
    public String url() {
      return BuiltInConnectionProperty.URL.wrap(properties).getString();
    }
  
    public String serialization() {
      return BuiltInConnectionProperty.SERIALIZATION.wrap(properties).getString();
    }
  
    public String authentication() {
      return BuiltInConnectionProperty.AUTHENTICATION.wrap(properties).getString();
    }
  
    public String avaticaUser() {
      return BuiltInConnectionProperty.AVATICA_USER.wrap(properties).getString();
    }
  
    public String avaticaPassword() {
      return BuiltInConnectionProperty.AVATICA_PASSWORD.wrap(properties).getString();
    }
  
    public AvaticaHttpClientFactory httpClientFactory() {
      return BuiltInConnectionProperty.HTTP_CLIENT_FACTORY.wrap(properties)
          .getPlugin(AvaticaHttpClientFactory.class, null);
    }
  
    public String httpClientClass() {
      return BuiltInConnectionProperty.HTTP_CLIENT_IMPL.wrap(properties).getString();
    }
  
    public String kerberosPrincipal() {
      return BuiltInConnectionProperty.PRINCIPAL.wrap(properties).getString();
    }
  
    public File kerberosKeytab() {
      String keytabPath = BuiltInConnectionProperty.KEYTAB.wrap(properties).getString();
      if (null == keytabPath) {
        return null;
      }
      File keytab = new File(keytabPath);
      if (!keytab.exists() || !keytab.isFile()) {
        throw new RuntimeException("The " + BuiltInConnectionProperty.KEYTAB.name() + " does not "
            + " reference a normal, existent file: " + keytabPath);
      }
      return keytab;
    }
  
    @Override
    public String keystoreType() {
      return BuiltInConnectionProperty.KEYSTORE_TYPE.wrap(properties).getString();
    }
  
    public File truststore() {
      String filename = BuiltInConnectionProperty.TRUSTSTORE.wrap(properties).getString();
      if (null == filename) {
        return null;
      }
      return new File(filename);
    }
  
    public String truststorePassword() {
      return BuiltInConnectionProperty.TRUSTSTORE_PASSWORD.wrap(properties).getString();
    }
  
    public File keystore() {
      String filename = BuiltInConnectionProperty.KEYSTORE.wrap(properties).getString();
      if (null == filename) {
        return null;
      }
      return new File(filename);
    }
  
    public String keystorePassword() {
      return BuiltInConnectionProperty.KEYSTORE_PASSWORD.wrap(properties).getString();
    }
  
    public String keyPassword() {
      return BuiltInConnectionProperty.KEY_PASSWORD.wrap(properties).getString();
  
    }
  
    @SuppressWarnings("deprecation")
    public org.apache.calcite.avatica.remote.
        HostnameVerificationConfigurable.HostnameVerification hostnameVerification() {
      return BuiltInConnectionProperty.HOSTNAME_VERIFICATION.wrap(properties)
          .getEnum(org.apache.calcite.avatica.remote.
              HostnameVerificationConfigurable.HostnameVerification.class);
    }
  
    @Override public boolean transparentReconnectionEnabled() {
      return BuiltInConnectionProperty.TRANSPARENT_RECONNECTION.wrap(properties)
         .getBoolean();
    }
  
    public int fetchSize() {
      return BuiltInConnectionProperty.FETCH_SIZE.wrap(properties).getInt();
    }
  
    @Override public boolean useClientSideLb() {
      return BuiltInConnectionProperty.USE_CLIENT_SIDE_LB.wrap(properties).getBoolean();
    }
  
    public String getLbURLs() {
      return BuiltInConnectionProperty.LB_URLS.wrap(properties).getString();
    }
  
    public LBStrategy getLBStrategy() {
      return BuiltInConnectionProperty.LB_STRATEGY.wrap(properties)
          .getPlugin(LBStrategy.class, null);
    }
  
    public int getLBConnectionFailoverRetries() {
      return BuiltInConnectionProperty.LB_CONNECTION_FAILOVER_RETRIES.wrap(properties).getInt();
    }
  
    public long getLBConnectionFailoverSleepTime() {
      return BuiltInConnectionProperty.LB_CONNECTION_FAILOVER_SLEEP_TIME.wrap(properties).getLong();
    }
  
    public long getHttpConnectionTimeout() {
      return BuiltInConnectionProperty.HTTP_CONNECTION_TIMEOUT.wrap(properties).getLong();
    }
  
    public long getHttpResponseTimeout() {
      return BuiltInConnectionProperty.HTTP_RESPONSE_TIMEOUT.wrap(properties).getLong();
    }
  
    /** Converts a {@link Properties} object containing (name, value)
     * pairs into a map whose keys are
     * {@link org.apache.calcite.avatica.InternalProperty} objects.
     *
     * <p>Matching is case-insensitive. Throws if a property is not known.
     * If a property occurs more than once, takes the last occurrence.</p>
     *
     * @param properties Properties
     * @return Map
     * @throws RuntimeException if a property is not known
     */
    public static Map<ConnectionProperty, String> parse(Properties properties,
        Map<String, ? extends ConnectionProperty> nameToProps) {
      final Map<ConnectionProperty, String> map =
          new LinkedHashMap<ConnectionProperty, String>();
      for (String name : properties.stringPropertyNames()) {
        final ConnectionProperty connectionProperty =
            nameToProps.get(name.toUpperCase(Locale.ROOT));
        if (connectionProperty == null) {
          // For now, don't throw. It messes up sub-projects.
          //throw new RuntimeException("Unknown property '" + name + "'");
          continue;
        }
        map.put(connectionProperty, properties.getProperty(name));
      }
      return map;
    }
  
    /** The combination of a property definition and a map of property values. */
    public static class PropEnv {
      final Map<? extends ConnectionProperty, String> map;
      private final ConnectionProperty property;
  
      public PropEnv(Map<? extends ConnectionProperty, String> map,
          ConnectionProperty property) {
        this.map = map;
        this.property = property;
      }
  
      private <T> T get_(Converter<T> converter, String defaultValue) {
        final String s = map.get(property);
        if (s != null) {
          return converter.apply(property, s);
        }
        return converter.apply(property, defaultValue);
      }
  
      private <T> T getDefaultNull(Converter<T> converter) {
        final String s = map.get(property);
        if (s != null) {
          return converter.apply(property, s);
        }
        return null;
      }
  
      /** Returns the string value of this property, or null if not specified and
       * no default. */
      public String getString() {
        return getString((String) property.defaultValue());
      }
  
      /** Returns the string value of this property, or null if not specified and
       * no default. */
      public String getString(String defaultValue) {
        assert property.type() == ConnectionProperty.Type.STRING;
        return get_(IDENTITY_CONVERTER, defaultValue);
      }
  
      /** Returns the int value of this property. Throws if not set and no
       * default. */
      public int getInt() {
        return getInt((Number) property.defaultValue());
      }
  
      /** Returns the int value of this property. Throws if not set and no
       * default. */
      public int getInt(Number defaultValue) {
        assert property.type() == ConnectionProperty.Type.NUMBER;
        return get_(NUMBER_CONVERTER, defaultValue.toString()).intValue();
      }
  
      /** Returns the long value of this property. Throws if not set and no
       * default. */
      public long getLong() {
        return getLong((Number) property.defaultValue());
      }
  
      /** Returns the long value of this property. Throws if not set and no
       * default. */
      public long getLong(Number defaultValue) {
        assert property.type() == ConnectionProperty.Type.NUMBER;
        return get_(NUMBER_CONVERTER, defaultValue.toString()).longValue();
      }
  
      /** Returns the double value of this property. Throws if not set and no
       * default. */
      public double getDouble() {
        return getDouble((Number) property.defaultValue());
      }
  
      /** Returns the double value of this property. Throws if not set and no
       * default. */
      public double getDouble(Number defaultValue) {
        assert property.type() == ConnectionProperty.Type.NUMBER;
        return get_(NUMBER_CONVERTER, defaultValue.toString()).doubleValue();
      }
  
      /** Returns the boolean value of this property. Throws if not set and no
       * default. */
      public boolean getBoolean() {
        return getBoolean((Boolean) property.defaultValue());
      }
  
      /** Returns the boolean value of this property. Throws if not set and no
       * default. */
      public boolean getBoolean(boolean defaultValue) {
        assert property.type() == ConnectionProperty.Type.BOOLEAN;
        return get_(BOOLEAN_CONVERTER, Boolean.toString(defaultValue));
      }
  
      /** Returns the enum value of this property. Throws if not set and no
       * default. */
      public <E extends Enum<E>> E getEnum(Class<E> enumClass) {
        //noinspection unchecked
        return getEnum(enumClass, (E) property.defaultValue());
      }
  
      /** Returns the enum value of this property. Throws if not set and no
       * default. */
      public <E extends Enum<E>> E getEnum(Class<E> enumClass, E defaultValue) {
        if (property.type() != ConnectionProperty.Type.ENUM) {
          throw new AssertionError("not an enum");
        }
        if (enumClass != property.valueClass()) {
          throw new AssertionError("wrong value class; expected "
              + property.valueClass());
        }
        if (defaultValue == null) {
          return getDefaultNull(enumConverter(enumClass));
        } else {
          return get_(enumConverter(enumClass), defaultValue.name());
        }
      }
  
      /** Returns an instance of a plugin.
       *
       * <p>Throws if not set and no default.
       * Also throws if the class does not implement the required interface,
       * or if it does not have a public default constructor or an public static
       * field called {@code #INSTANCE}. */
      public <T> T getPlugin(Class<T> pluginClass, T defaultInstance) {
        return getPlugin(pluginClass, (String) property.defaultValue(),
            defaultInstance);
      }
  
      /** Returns an instance of a plugin, using a given class name if none is
       * set.
       *
       * <p>Throws if not set and no default.
       * Also throws if the class does not implement the required interface,
       * or if it does not have a public default constructor or an public static
       * field called {@code #INSTANCE}. */
      public <T> T getPlugin(Class<T> pluginClass, String defaultClassName,
          T defaultInstance) {
        assert property.type() == ConnectionProperty.Type.PLUGIN;
        return get_(pluginConverter(pluginClass, defaultInstance),
            defaultClassName);
      }
    }
  
    /** Callback to parse a property from string to its native type.
     * @param <T> the native type */
    public interface Converter<T> {
      T apply(ConnectionProperty connectionProperty, String s);
    }
  
    public static final Converter<Boolean> BOOLEAN_CONVERTER =
        new Converter<Boolean>() {
          public Boolean apply(ConnectionProperty connectionProperty, String s) {
            if (s == null) {
              throw new RuntimeException("Required property '"
                  + connectionProperty.camelName() + "' not specified");
            }
            return Boolean.parseBoolean(s);
          }
        };
  
    static final Map<String, BigDecimal> MULTIPLIER_MAP =
        new LinkedHashMap<>();
    static {
      MULTIPLIER_MAP.put("k", new BigDecimal(1024));
      MULTIPLIER_MAP.put("K", new BigDecimal(1024));
      MULTIPLIER_MAP.put("m", new BigDecimal(1024 * 1024));
      MULTIPLIER_MAP.put("M", new BigDecimal(1024 * 1024));
      MULTIPLIER_MAP.put("g", new BigDecimal(1024 * 1024 * 1024));
      MULTIPLIER_MAP.put("G", new BigDecimal(1024 * 1024 * 1024));
    }
  
    public static final Converter<Number> NUMBER_CONVERTER =
        new Converter<Number>() {
          public Number apply(ConnectionProperty connectionProperty, String s) {
            if (s == null) {
              throw new RuntimeException("Required property '"
                  + connectionProperty.camelName() + "' not specified");
            }
            BigDecimal multiplier = BigDecimal.ONE;
            for (Map.Entry<String, BigDecimal> e : MULTIPLIER_MAP.entrySet()) {
              if (s.endsWith(e.getKey())) {
                multiplier = e.getValue();
                s = s.substring(0, s.length() - e.getKey().length());
              }
            }
            return new BigDecimal(s).multiply(multiplier);
          }
        };
  
    public static final Converter<String> IDENTITY_CONVERTER =
        new Converter<String>() {
          public String apply(ConnectionProperty connectionProperty, String s) {
            return s;
          }
        };
  
    public static <E extends Enum> Converter<E> enumConverter(
        final Class<E> enumClass) {
      return new Converter<E>() {
        public E apply(ConnectionProperty connectionProperty, String s) {
          if (s == null) {
            throw new RuntimeException("Required property '"
                + connectionProperty.camelName() + "' not specified");
          }
          try {
            return (E) Enum.valueOf(enumClass, s);
          } catch (IllegalArgumentException e) {
            // Case insensitive match is OK too.
            for (E c : enumClass.getEnumConstants()) {
              if (c.name().equalsIgnoreCase(s)) {
                return c;
              }
            }
            throw new RuntimeException("Property '" + s + "' not valid for enum "
                + enumClass.getName());
          }
        }
      };
    }
  
    public static <T> Converter<T> pluginConverter(final Class<T> pluginClass,
        final T defaultInstance) {
      return new Converter<T>() {
        public T apply(ConnectionProperty connectionProperty, String s) {
          if (s == null) {
            if (defaultInstance != null) {
              return defaultInstance;
            }
            if (!connectionProperty.required()) {
              return null;
            }
            throw new RuntimeException("Required property '"
                + connectionProperty.camelName() + "' not specified");
          }
          return AvaticaUtils.instantiatePlugin(pluginClass, s);
        }
      };
    }
  }
  
  // End ConnectionConfigImpl.java
  