@Test
  public void testWithMultipleTargetValueResults() throws Exception {
    final TestExtractor extractor =
        new TestExtractor.Builder()
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {
                      new Result(1, "one", -1, -1),
                      new Result("2", "two", -1, -1),
                      new Result(3, "three", -1, -1)
                    };
                  }
                })
            .build();

    final Message msg = createMessage("the hello");

    extractor.runExtractor(msg);

    assertThat(msg.hasField("target")).isFalse();
    assertThat(msg.getField("one")).isEqualTo(1);
    assertThat(msg.getField("two")).isEqualTo("2");
    assertThat(msg.getField("three")).isEqualTo(3);
  }
  @Test
  public void testConvertersWithMultipleFields() throws Exception {
    final Converter converter =
        new TestConverter.Builder()
            .multiple(true)
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    return ImmutableMap.builder()
                        .put("one", 1)
                        .put("two", "2")
                        .put(
                            "message",
                            "message should not be overwritten") // Try to overwrite reserved field.
                        .build();
                  }
                })
            .build();

    final TestExtractor extractor =
        new TestExtractor.Builder()
            .converters(Lists.newArrayList(converter))
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("1", -1, -1)};
                  }
                })
            .build();

    final Message msg = createMessage("the message");

    extractor.runExtractor(msg);

    // With a "multiple fields" converter the target field is not touched, only the additional
    // fields are added.
    assertThat(msg.getField("target")).isEqualTo("1");
    assertThat(msg.getField("one")).isEqualTo(1);
    assertThat(msg.getField("two")).isEqualTo("2");

    // Reserved fields are not overwritten!
    assertThat(msg.getField("message")).isEqualTo("the message");

    // Attempts to overwrite a reserved field are recorded as converter exception.
    assertThat(extractor.getConverterExceptionCount()).isEqualTo(1);
  }
  @Test
  public void testConvertersThatReturnNullValue() throws Exception {
    final Converter converter =
        new TestConverter.Builder()
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    return null;
                  }
                })
            .build();

    final TestExtractor extractor =
        new TestExtractor.Builder()
            .converters(Lists.newArrayList(converter))
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("1", -1, -1)};
                  }
                })
            .build();

    final Message msg = createMessage("message");

    extractor.runExtractor(msg);

    assertThat(msg.getField("target")).isNull();
  }
  @Test
  public void testConvertersWithNonStringFieldValue() throws Exception {
    final Converter converter =
        new TestConverter.Builder()
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    return "converted";
                  }
                })
            .build();

    final TestExtractor extractor =
        new TestExtractor.Builder()
            .converters(Lists.newArrayList(converter))
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result(123, "target", -1, -1)};
                  }
                })
            .build();

    final Message msg = createMessage("message");

    extractor.runExtractor(msg);

    // Only string values will be converted.
    assertThat(msg.getField("target")).isEqualTo(123);
  }
  @Test
  public void testCursorStrategyCutIfTargetFieldEqualsSourceField() throws Exception {
    final TestExtractor extractor =
        new TestExtractor.Builder()
            .cursorStrategy(CUT)
            .sourceField("msg")
            .targetField("msg")
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("the", 0, 3)};
                  }
                })
            .build();

    final Message msg = createMessage("message");
    msg.addField("msg", "the hello");

    extractor.runExtractor(msg);

    // If source and target fields are the same, the field is not touched because it already got set
    // to a new value.
    assertThat(msg.getField("msg")).isEqualTo("the");
  }
  @Test
  public void testCursorStrategyCutWithMultipleResults() throws Exception {
    final TestExtractor extractor =
        new TestExtractor.Builder()
            .cursorStrategy(CUT)
            .sourceField("msg")
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {
                      new Result("the", "one", 0, 3), new Result("hello", "two", 10, 15),
                    };
                  }
                })
            .build();

    final Message msg = createMessage("message");
    msg.addField("msg", "the great hello");

    extractor.runExtractor(msg);

    // With the cut strategy the matched data will be removed from the message.
    assertThat(msg.getField("msg")).isEqualTo("great");
  }
  @Test
  public void testCursorStrategyCopyWithMultipleResults() throws Exception {
    final TestExtractor extractor =
        new TestExtractor.Builder()
            .cursorStrategy(COPY)
            .sourceField("msg")
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {
                      new Result("the", "one", 0, 3), new Result("hello", "two", 10, 15),
                    };
                  }
                })
            .build();

    final Message msg = createMessage("message");
    msg.addField("msg", "the great hello");

    extractor.runExtractor(msg);

    // With the copy strategy, the source field will not be modified.
    assertThat(msg.getField("msg")).isEqualTo("the great hello");
  }
  @Test
  public void testConvertersAreExecutedInOrder() throws Exception {
    final Converter converter1 =
        new TestConverter.Builder()
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    return ((String) input) + "1";
                  }
                })
            .build();

    final Converter converter2 =
        new TestConverter.Builder()
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    return ((String) input) + "2";
                  }
                })
            .build();

    final Converter converter3 =
        new TestConverter.Builder()
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    return ((String) input) + "3";
                  }
                })
            .build();

    final TestExtractor extractor =
        new TestExtractor.Builder()
            .converters(Lists.newArrayList(converter1, converter2, converter3))
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("converter", -1, -1)};
                  }
                })
            .build();

    final Message msg = createMessage("message");

    extractor.runExtractor(msg);

    assertThat(msg.getField("target")).isEqualTo("converter123");
  }
  @Override
  public boolean match(Message msg, StreamRule rule) {
    Double msgVal = getDouble(msg.getField(rule.getField()));
    if (msgVal == null) {
      return false;
    }

    Double ruleVal = getDouble(rule.getValue());
    if (ruleVal == null) {
      return false;
    }

    return rule.getInverted() ^ (msgVal > ruleVal);
  }
  @Test
  public void testMultipleConvertersWithFirstReturningNullValue() throws Exception {
    final Converter converter1 =
        new TestConverter.Builder()
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    return null;
                  }
                })
            .build();

    final Converter converter2 =
        new TestConverter.Builder()
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    return input + "2";
                  }
                })
            .build();

    final TestExtractor extractor =
        new TestExtractor.Builder()
            .converters(Lists.newArrayList(converter1, converter2))
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("converter", -1, -1)};
                  }
                })
            .build();

    final Message msg = createMessage("message");

    extractor.runExtractor(msg);

    // If the first converter returns null, the second will not be executed because the value is not
    // a string anymore.
    assertThat(msg.getField("target")).isNull();
  }
  @Test
  public void testWithOneValueOnlyResult() throws Exception {
    final TestExtractor extractor =
        new TestExtractor.Builder()
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("1", -1, -1)};
                  }
                })
            .build();

    final Message msg = createMessage("the hello");

    extractor.runExtractor(msg);

    assertThat(msg.getField("target")).isEqualTo("1");
  }
  @Test
  public void testCursorStrategyCutIfSourceFieldIsReservedField() throws Exception {
    final TestExtractor extractor =
        new TestExtractor.Builder()
            .cursorStrategy(CUT)
            .sourceField("message")
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("the", 0, 3)};
                  }
                })
            .build();

    final Message msg = createMessage("the hello");

    extractor.runExtractor(msg);

    // The source value is not modified if it is a reserved field.
    assertThat(msg.getField("message")).isEqualTo("the hello");
  }
  @Test
  public void testCursorStrategyCutIfBeginAndEndIndexAreDisabled() throws Exception {
    final TestExtractor extractor =
        new TestExtractor.Builder()
            .cursorStrategy(CUT)
            .sourceField("msg")
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("the", -1, -1)};
                  }
                })
            .build();

    final Message msg = createMessage("message");
    msg.addField("msg", "the hello");

    extractor.runExtractor(msg);

    // If the begin and end index is -1, the source field should not be modified.
    assertThat(msg.getField("msg")).isEqualTo("the hello");
  }
  @Test
  public void testCursorStrategyCutWithAllTextCut() throws Exception {
    final TestExtractor extractor =
        new TestExtractor.Builder()
            .cursorStrategy(CUT)
            .sourceField("msg")
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("the hello", 0, 9)};
                  }
                })
            .build();

    final Message msg = createMessage("message");
    msg.addField("msg", "the hello");

    extractor.runExtractor(msg);

    // If all data is cut from the source field, the "fullyCutByExtractor" string gets inserted.
    assertThat(msg.getField("msg")).isEqualTo("fullyCutByExtractor");
  }
  @Test(expected = StringIndexOutOfBoundsException.class)
  public void testCursorStrategyCutIfEndIndexIsDisabled() throws Exception {
    final TestExtractor extractor =
        new TestExtractor.Builder()
            .cursorStrategy(CUT)
            .sourceField("msg")
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("the", 0, -1)};
                  }
                })
            .build();

    final Message msg = createMessage("message");
    msg.addField("msg", "the hello");

    extractor.runExtractor(msg);

    // If the end index is -1, the source field should not be modified.
    // TODO: The current implementation only checks if begin index is -1. Needs to be fixed.
    assertThat(msg.getField("msg")).isEqualTo("the hello");
  }
  @Override
  public void write(Message msg) throws Exception {
    if (shutdown || driverFailed) {
      return;
    }

    try {
      if (connection == null) {
        reconnect();
      }

      if (connection == null) {
        return;
      }

      synchronized (connection) {
        int index = 1;
        logInsert.setTimestamp(index++, new Timestamp(msg.getTimestamp().getMillis()));
        logInsert.setString(index++, msg.getId());
        logInsert.setString(index++, msg.getSource());
        String ms = msg.getMessage();
        if (ms != null && ms.length() > MAX_MESSAGE) {
          ms = ms.substring(0, MAX_MESSAGE);
        }
        logInsert.setString(index++, ms);

        if (fields != null) {
          for (String f : fields) {
            Object value = msg.getField(f);
            String s = value != null ? value.toString() : null;
            if (s == null) {
              logInsert.setNull(index++, Types.VARCHAR);
            } else {
              if (s.length() > MAX_VALUE) {
                s = s.substring(0, MAX_VALUE);
              }
              logInsert.setString(index++, s);
            }
          }
        }

        logInsert.executeUpdate();

        if (logInsertAttribute != null) {
          Object id = null;
          ResultSet ids = logInsert.getGeneratedKeys();
          while (ids != null && ids.next()) {
            id = ids.getObject(1);
          }
          if (id != null) {
            for (Entry<String, Object> e : msg.getFieldsEntries()) {
              String name = e.getKey();
              Object value = e.getValue();
              String s = value != null ? value.toString() : null;
              logInsertAttribute.setObject(1, id);
              logInsertAttribute.setString(2, name);
              if (s.length() > MAX_VALUE) {
                s = s.substring(0, MAX_VALUE);
              }
              logInsertAttribute.setString(3, s);
              logInsertAttribute.executeUpdate();
            }
          } else {
            throw new SQLException("Failed to generate ID for primary log record!");
          }
        }
      }
    } catch (SQLException e) {
      log.log(Level.WARNING, "JDBC output error: " + e.getMessage(), e);
      if (connection != null) {
        try {
          connection.rollback();
          connection.setAutoCommit(true);
        } catch (SQLException ee) {
          // Don`t care
        }
      }
      connection = null;
    } finally {
      if (connection != null) {
        connection.commit();
      }
    }
  }
  @Test
  public void testConvertersWithExceptions() throws Exception {
    final Converter converter1 =
        new TestConverter.Builder()
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    throw new NullPointerException("EEK");
                  }
                })
            .build();

    final Converter converter2 =
        new TestConverter.Builder()
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    return input + "2";
                  }
                })
            .build();

    final Converter converter3 =
        new TestConverter.Builder()
            .callback(
                new Function<Object, Object>() {
                  @Nullable
                  @Override
                  public Object apply(Object input) {
                    throw new NullPointerException("EEK");
                  }
                })
            .build();

    final TestExtractor extractor =
        new TestExtractor.Builder()
            .converters(Lists.newArrayList(converter1, converter2, converter3))
            .callback(
                new Callable<Result[]>() {
                  @Override
                  public Result[] call() throws Exception {
                    return new Result[] {new Result("converter", -1, -1)};
                  }
                })
            .build();

    final Message msg = createMessage("message");

    extractor.runExtractor(msg);

    // The two exceptions should have been recorded.
    assertThat(extractor.getConverterExceptionCount()).isEqualTo(2);

    // It ignores all converters which throw an exception but executes the ones that don't.
    // TODO: Is this really the expected behaviour? The converters are executed in order and
    // basically depend on the output of the previous. This might not work for all converters.
    assertThat(msg.getField("target")).isEqualTo("converter2");
  }