Example #1
0
  /**
   * Create a new client without any initial connections. Also provide a hint indicating the
   * expected serialized size of most outgoing procedure invocations. This helps size initial
   * allocations for serializing network writes
   *
   * @param expectedOutgoingMessageSize Expected size of procedure invocations in bytes
   * @param maxArenaSizes Maximum size arenas in the memory pool should grow to
   * @param heavyweight Whether to use multiple or a single thread
   */
  ClientImpl(ClientConfig config) {
    m_distributer =
        new Distributer(
            config.m_heavyweight,
            config.m_procedureCallTimeoutNanos,
            config.m_connectionResponseTimeoutMS,
            config.m_useClientAffinity,
            config.m_subject);
    m_distributer.addClientStatusListener(new CSL());
    String username = config.m_username;
    if (config.m_subject != null) {
      username = config.m_subject.getPrincipals().iterator().next().getName();
    }
    m_username = username;

    if (config.m_cleartext) {
      m_passwordHash = ConnectionUtil.getHashedPassword(config.m_password);
    } else {
      m_passwordHash = Encoder.hexDecode(config.m_password);
    }
    if (config.m_listener != null) {
      m_distributer.addClientStatusListener(config.m_listener);
    }
    assert (config.m_maxOutstandingTxns > 0);
    m_blessedThreadIds.addAll(m_distributer.getThreadIds());
    if (config.m_autoTune) {
      m_distributer.m_rateLimiter.enableAutoTuning(config.m_autoTuneTargetInternalLatency);
    } else {
      m_distributer.m_rateLimiter.setLimits(
          config.m_maxTransactionsPerSecond, config.m_maxOutstandingTxns);
    }
  }
  /**
   * Factored out code to handle array parameter types.
   *
   * @throws Exception with a message describing why the types are incompatible.
   */
  private static Object tryToMakeCompatibleArray(
      final Class<?> expectedComponentClz, final Class<?> inputComponentClz, Object param)
      throws Exception {
    int inputLength = Array.getLength(param);

    if (inputComponentClz == expectedComponentClz) {
      return param;
    }
    // if it's an empty array, let it through
    // this is a bit ugly as it might hide passing
    //  arrays of the wrong type, but it "does the right thing"
    //  more often that not I guess...
    else if (inputLength == 0) {
      return Array.newInstance(expectedComponentClz, 0);
    }
    // hack to make strings work with input as bytes
    else if ((inputComponentClz == byte[].class) && (expectedComponentClz == String.class)) {
      String[] values = new String[inputLength];
      for (int i = 0; i < inputLength; i++) {
        values[i] = new String((byte[]) Array.get(param, i), "UTF-8");
      }
      return values;
    }
    // hack to make varbinary work with input as hex string
    else if ((inputComponentClz == String.class) && (expectedComponentClz == byte[].class)) {
      byte[][] values = new byte[inputLength][];
      for (int i = 0; i < inputLength; i++) {
        values[i] = Encoder.hexDecode((String) Array.get(param, i));
      }
      return values;
    } else {
      /*
       * Arrays can be quite large so it doesn't make sense to silently do the conversion
       * and incur the performance hit. The client should serialize the correct invocation
       * parameters
       */
      throw new Exception(
          "tryScalarMakeCompatible: Unable to match parameter array:"
              + expectedComponentClz.getName()
              + " to provided "
              + inputComponentClz.getName());
    }
  }
  /**
   * Convert the given value to the type given, if possible.
   *
   * <p>This function is in the performance path, so some effort has been made to order the giant
   * string of branches such that most likely things are first, and that if the type is already
   * correct, it should move very quickly through the logic. Some clarity has been sacrificed for
   * performance, but perfect clarity is pretty elusive with complicated logic like this anyway.
   *
   * @throws Exception with a message describing why the types are incompatible.
   */
  public static Object tryToMakeCompatible(final Class<?> expectedClz, final Object param)
      throws Exception {
    // uncomment for debugging
    /*System.err.printf("Converting %s of type %s to type %s\n",
            String.valueOf(param),
            param == null ? "NULL" : param.getClass().getName(),
            paramType.getName());
    System.err.flush();*/

    // Get blatant null out of the way fast, as it avoids some inline checks
    // There are some suble null values that aren't java null coming up, but wait until
    // after the basics to check for those.
    if (param == null) {
      return nullValueForType(expectedClz);
    }

    Class<?> inputClz = param.getClass();

    // If we make it through this first block, memoize a number value for some range checks later
    Number numberParam = null;

    // This first code block tries to hit as many common cases as possible
    // Specifically, it does primitive types and strings, which are the most common param types.
    // Downconversions (e.g. long => short) happen later, but can use the memoized numberParam
    // value.
    // Notice this block switches on the type of the given value (different later).

    if (inputClz == Long.class) {
      if (expectedClz == long.class) return param;
      if ((Long) param == VoltType.NULL_BIGINT) return nullValueForType(expectedClz);
      numberParam = (Number) param;
    } else if (inputClz == Integer.class) {
      if (expectedClz == int.class) return param;
      if ((Integer) param == VoltType.NULL_INTEGER) return nullValueForType(expectedClz);
      if (expectedClz == long.class) return ((Integer) param).longValue();
      numberParam = (Number) param;
    } else if (inputClz == Short.class) {
      if (expectedClz == short.class) return param;
      if ((Short) param == VoltType.NULL_SMALLINT) return nullValueForType(expectedClz);
      if (expectedClz == long.class) return ((Short) param).longValue();
      if (expectedClz == int.class) return ((Short) param).intValue();
      numberParam = (Number) param;
    } else if (inputClz == Byte.class) {
      if (expectedClz == byte.class) return param;
      if ((Byte) param == VoltType.NULL_TINYINT) return nullValueForType(expectedClz);
      if (expectedClz == long.class) return ((Byte) param).longValue();
      if (expectedClz == int.class) return ((Byte) param).intValue();
      if (expectedClz == short.class) return ((Byte) param).shortValue();
      numberParam = (Number) param;
    } else if (inputClz == Double.class) {
      if (expectedClz == double.class) return param;
      if ((Double) param == VoltType.NULL_FLOAT) return nullValueForType(expectedClz);
    } else if (inputClz == String.class) {
      if (((String) param).equals(VoltTable.CSV_NULL)) return nullValueForType(expectedClz);
      else if (expectedClz == String.class) return param;
      // Hack allows hex-encoded strings to be passed into byte[] params
      else if (expectedClz == byte[].class) {
        return Encoder.hexDecode((String) param);
      }
      // We allow all values to be passed as strings for csv loading, json, etc...
      // This code handles primitive types. Complex types come later.
      if (expectedClz.isPrimitive()) {
        return convertStringToPrimitive((String) param, expectedClz);
      }
    } else if (inputClz == byte[].class) {
      if (expectedClz == byte[].class) return param;
      // allow byte arrays to be passed into string parameters
      else if (expectedClz == String.class) {
        String value = new String((byte[]) param, Charsets.UTF_8);
        if (value.equals(VoltTable.CSV_NULL)) return nullValueForType(expectedClz);
        else return value;
      }
    }
    // null sigil
    else if (param == VoltType.NULL_STRING_OR_VARBINARY) {
      return nullValueForType(expectedClz);
    }
    // null sigil
    else if (param == VoltType.NULL_DECIMAL) {
      return nullValueForType(expectedClz);
    }
    // these are used by system procedures and are ignored here
    else if (param instanceof SystemProcedureExecutionContext) {
      return param;
    }

    // make sure we get the array/scalar match
    if (expectedClz.isArray() != inputClz.isArray()) {
      throw new Exception(
          String.format(
              "Array / Scalar parameter mismatch (%s to %s)",
              inputClz.getName(), expectedClz.getName()));
    }

    // handle arrays in a factored-out method
    if (expectedClz.isArray()) {
      return tryToMakeCompatibleArray(
          expectedClz.getComponentType(), inputClz.getComponentType(), param);
    }

    // The following block switches on the type of the paramter desired.
    // It handles all of the paths not trapped in the code above. We can assume
    // values are not null and that most sane primitive stuff has been handled.
    // Downcasting is handled here (e.g. long => short).
    // Time (in many forms) and Decimal are also handled below.

    if ((expectedClz == int.class) && (numberParam != null)) {
      long val = numberParam.longValue();
      if (val == VoltType.NULL_INTEGER) {
        throw new Exception(
            "tryToMakeCompatible: The provided long value: ("
                + param.toString()
                + ") might be interpreted as integer null. "
                + "Try explicitly using a int parameter.");
      }
      // if it's in the right range, crop the value and return
      if ((val <= Integer.MAX_VALUE) && (val >= Integer.MIN_VALUE)) return numberParam.intValue();
    } else if ((expectedClz == short.class) && (numberParam != null)) {
      if ((inputClz == Long.class) || (inputClz == Integer.class)) {
        long val = numberParam.longValue();
        if (val == VoltType.NULL_SMALLINT) {
          throw new Exception(
              "tryToMakeCompatible: The provided int or long value: ("
                  + param.toString()
                  + ") might be interpreted as smallint null. "
                  + "Try explicitly using a short parameter.");
        }
        // if it's in the right range, crop the value and return
        if ((val <= Short.MAX_VALUE) && (val >= Short.MIN_VALUE)) return numberParam.shortValue();
      }
    } else if ((expectedClz == byte.class) && (numberParam != null)) {
      if ((inputClz == Long.class) || (inputClz == Integer.class) || (inputClz == Short.class)) {
        long val = numberParam.longValue();
        if (val == VoltType.NULL_TINYINT) {
          throw new Exception(
              "tryToMakeCompatible: The provided short, int or long value: ("
                  + param.toString()
                  + ") might be interpreted as tinyint null. "
                  + "Try explicitly using a byte parameter.");
        }
        // if it's in the right range, crop the value and return
        if ((val <= Byte.MAX_VALUE) && (val >= Byte.MIN_VALUE)) return numberParam.byteValue();
      }
    } else if ((expectedClz == double.class) && (numberParam != null)) {
      return numberParam.doubleValue();
    } else if (expectedClz == TimestampType.class) {
      if (inputClz == Long.class) return new TimestampType((Long) param); // null values safe
      if (inputClz == TimestampType.class) return param;
      if (inputClz == Date.class) return new TimestampType((Date) param);
      // if a string is given for a date, use java's JDBC parsing
      if (inputClz == String.class) {
        String longtime = ((String) param).trim();
        try {
          return new TimestampType(Long.parseLong(longtime));
        } catch (IllegalArgumentException e) {
          // Defer errors to the generic Exception throw below, if it's not the right format
        }
        try {
          return new TimestampType(longtime);
        } catch (IllegalArgumentException e) {
          // Defer errors to the generic Exception throw below, if it's not the right format
        }
      }
    } else if (expectedClz == java.sql.Timestamp.class) {
      if (param instanceof java.sql.Timestamp) return param;
      if (param instanceof java.util.Date)
        return new java.sql.Timestamp(((java.util.Date) param).getTime());
      if (param instanceof TimestampType) return ((TimestampType) param).asJavaTimestamp();
      // If a string is given for a date, use java's JDBC parsing.
      if (inputClz == String.class) {
        String longtime = ((String) param).trim();
        try {
          return new java.sql.Timestamp(Long.parseLong(longtime));
        } catch (IllegalArgumentException e) {
          // Defer errors to the generic Exception throw below, if it's not the right format
        }
        try {
          return java.sql.Timestamp.valueOf(longtime);
        } catch (IllegalArgumentException e) {
          // Defer errors to the generic Exception throw below, if it's not the right format
        }
      }
    } else if (expectedClz == java.sql.Date.class) {
      if (param instanceof java.sql.Date)
        return param; // covers java.sql.Date and java.sql.Timestamp
      if (param instanceof java.util.Date)
        return new java.sql.Date(((java.util.Date) param).getTime());
      if (param instanceof TimestampType) return ((TimestampType) param).asExactJavaSqlDate();
      // If a string is given for a date, use java's JDBC parsing.
      if (inputClz == String.class) {
        try {
          return new java.sql.Date(TimestampType.millisFromJDBCformat((String) param));
        } catch (IllegalArgumentException e) {
          // Defer errors to the generic Exception throw below, if it's not the right format
        }
      }
    } else if (expectedClz == java.util.Date.class) {
      if (param instanceof java.util.Date)
        return param; // covers java.sql.Date and java.sql.Timestamp
      if (param instanceof TimestampType) return ((TimestampType) param).asExactJavaDate();
      // If a string is given for a date, use the default format parser for the default locale.
      if (inputClz == String.class) {
        try {
          return new java.util.Date(TimestampType.millisFromJDBCformat((String) param));
        } catch (IllegalArgumentException e) {
          // Defer errors to the generic Exception throw below, if it's not the right format
        }
      }
    } else if (expectedClz == BigDecimal.class) {
      if (numberParam != null) {
        BigInteger bi = new BigInteger(param.toString());
        BigDecimal bd = new BigDecimal(bi);
        bd = VoltDecimalHelper.setDefaultScale(bd);
        return bd;
      }
      if (inputClz == BigDecimal.class) {
        BigDecimal bd = (BigDecimal) param;
        bd = VoltDecimalHelper.setDefaultScale(bd);
        return bd;
      }
      if (inputClz == Float.class || inputClz == Double.class) {
        return VoltDecimalHelper.deserializeBigDecimalFromString(String.format("%.12f", param));
      }
      return VoltDecimalHelper.deserializeBigDecimalFromString(String.valueOf(param));
    } else if (expectedClz == VoltTable.class && inputClz == VoltTable.class) {
      return param;
    }

    throw new Exception(
        "tryToMakeCompatible: The provided value: ("
            + param.toString()
            + ") of type: "
            + inputClz.getName()
            + " is not a match or is out of range for the target parameter type: "
            + expectedClz.getName());
  }