mirror of
https://github.com/elastic/logstash.git
synced 2025-04-24 22:57:16 -04:00
WIP removing the need to specify argument positions explicitly like (String) arg[0]
for constructors.
This commit is contained in:
parent
b656f2dce3
commit
24a2308774
12 changed files with 407 additions and 260 deletions
|
@ -1,6 +1,15 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
import org.logstash.common.parser.Functions.Function3;
|
||||
import org.logstash.common.parser.Functions.Function4;
|
||||
import org.logstash.common.parser.Functions.Function5;
|
||||
import org.logstash.common.parser.Functions.Function6;
|
||||
import org.logstash.common.parser.Functions.Function7;
|
||||
import org.logstash.common.parser.Functions.Function8;
|
||||
import org.logstash.common.parser.Functions.Function9;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -21,7 +30,7 @@ import java.util.stream.Collectors;
|
|||
public class ConstructingObjectParser<Value> implements ObjectParser<Value> {
|
||||
private final Map<String, BiConsumer<Value, Object>> parsers = new HashMap<>();
|
||||
private List<Field<?>> constructorFields = null;
|
||||
private Function<Map<String, Object>, Value> builder;
|
||||
private final Function<Map<String, Object>, Value> builder;
|
||||
/**
|
||||
* Zero-argument object constructor (A Supplier)
|
||||
* @param supplier The supplier which produces an object instance.
|
||||
|
@ -29,14 +38,16 @@ public class ConstructingObjectParser<Value> implements ObjectParser<Value> {
|
|||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public ConstructingObjectParser(Supplier<Value> supplier) {
|
||||
this(config -> supplier.get());
|
||||
constructorFields = Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* One-argument object constructor
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public <Arg0> ConstructingObjectParser(Function<Arg0, Value> function, Field<Arg0> arg0) {
|
||||
this(config -> function.apply(arg0.apply(config)));
|
||||
constructorFields = Arrays.asList(arg0);
|
||||
constructorFields = Collections.singletonList(arg0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,7 +159,7 @@ public class ConstructingObjectParser<Value> implements ObjectParser<Value> {
|
|||
}
|
||||
|
||||
BiConsumer<Value, Object> objConsumer = (value, object) -> consumer.accept(value, transform.apply(object));
|
||||
FieldDefinition<T> field = new FieldDefinition<T>(name, transform);
|
||||
FieldDefinition<T> field = new FieldDefinition<>(name, transform);
|
||||
parsers.put(name, (value, input) -> consumer.accept(value, transform.apply(input)));
|
||||
return field;
|
||||
}
|
||||
|
@ -198,29 +209,6 @@ public class ConstructingObjectParser<Value> implements ObjectParser<Value> {
|
|||
return parsers.containsKey(name) || isConstructorField(name);
|
||||
}
|
||||
|
||||
public interface Function3<Arg0, Arg1, Arg2, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2);
|
||||
}
|
||||
|
||||
public interface Function4<Arg0, Arg1, Arg2, Arg3, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param builder A function which takes an Object[] as argument and returns a Value instance
|
||||
*/
|
||||
//@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
//public ConstructingObjectParser(Function<Object[], Value> builder, Field<?>... fields) {
|
||||
//this(config -> construct(config, builder, fields), fields);
|
||||
//}
|
||||
|
||||
public interface Function5<Arg0, Arg1, Arg2, Arg3, Arg4, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4);
|
||||
}
|
||||
|
||||
public interface Function6<Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5);
|
||||
}
|
||||
|
||||
private void rejectUnknownFields(Set<String> configNames) throws IllegalArgumentException {
|
||||
// Check for any unknown parameters.
|
||||
|
@ -231,15 +219,4 @@ public class ConstructingObjectParser<Value> implements ObjectParser<Value> {
|
|||
}
|
||||
}
|
||||
|
||||
public interface Function7<Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6);
|
||||
}
|
||||
|
||||
public interface Function8<Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7);
|
||||
}
|
||||
|
||||
public interface Function9<Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,10 @@ public interface Field<Value> extends Function<Object, Value> {
|
|||
return declareField(name, ObjectTransforms::transformMap);
|
||||
}
|
||||
|
||||
static <V> Field<V> declareObject(String name, ConstructingObjectParser<V> parser) {
|
||||
return declareField(name, (config) -> parser.apply(ObjectTransforms.transformMap(config)));
|
||||
}
|
||||
|
||||
static <V> Field<List<V>> declareList(String name, Function<Object, V> transform) {
|
||||
return new FieldDefinition<>(name, (object) -> ObjectTransforms.transformList(object, transform));
|
||||
}
|
||||
|
@ -44,10 +48,6 @@ public interface Field<Value> extends Function<Object, Value> {
|
|||
return new FieldDefinition<>(name, transform);
|
||||
}
|
||||
|
||||
boolean isDeprecated();
|
||||
|
||||
boolean isObsolete();
|
||||
|
||||
String getName();
|
||||
|
||||
String getDetails();
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
class FieldDefinition<Value> implements Field<Value> {
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
private final Function<Object, Value> transform;
|
||||
|
||||
private String name;
|
||||
private final String name;
|
||||
|
||||
// This is only set if deprecated or obsolete
|
||||
// XXX: Move this concept separately to DeprecatedFieldDefinition and ObsoleteFieldDefinition
|
||||
private FieldStatus status;
|
||||
private FieldStatus status = FieldStatus.Supported;
|
||||
private String details;
|
||||
|
||||
FieldDefinition(String name, Function<Object, Value> transform) {
|
||||
|
@ -36,19 +40,24 @@ class FieldDefinition<Value> implements Field<Value> {
|
|||
|
||||
@Override
|
||||
public Value apply(Object object) {
|
||||
if (object == null) {
|
||||
throw new NullPointerException("The '" + name + "' field is required and no value was provided.");
|
||||
}
|
||||
switch (status) {
|
||||
// XXX: use Structured logging + localization lookups.
|
||||
case Deprecated:
|
||||
logger.warn("The field '" + getName() + "' is deprecated and will be removed soon: " + getDetails());
|
||||
break;
|
||||
case Obsolete:
|
||||
logger.fatal("The field '" + getName() + "' is obsolete and has been removed: " + getDetails());
|
||||
break;
|
||||
case Supported:
|
||||
break;
|
||||
}
|
||||
|
||||
return transform.apply(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeprecated() {
|
||||
return status == FieldStatus.Deprecated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObsolete() {
|
||||
return status == FieldStatus.Obsolete;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
class Functions {
|
||||
public interface Function3<Arg0, Arg1, Arg2, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2);
|
||||
}
|
||||
|
||||
public interface Function4<Arg0, Arg1, Arg2, Arg3, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3);
|
||||
}
|
||||
|
||||
public interface Function5<Arg0, Arg1, Arg2, Arg3, Arg4, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4);
|
||||
}
|
||||
|
||||
public interface Function6<Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5);
|
||||
}
|
||||
|
||||
public interface Function7<Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6);
|
||||
}
|
||||
|
||||
public interface Function8<Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7);
|
||||
}
|
||||
|
||||
public interface Function9<Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Value> {
|
||||
Value apply(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7, Arg8 arg8);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ import org.junit.experimental.runners.Enclosed;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -17,8 +16,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
|
@ -30,9 +28,9 @@ public class ConstructingObjectParserTest {
|
|||
}
|
||||
|
||||
public static class MixedUsageTest {
|
||||
private Map<String, Object> config = new HashMap<>();
|
||||
private int foo = 1000; // XXX: randomize
|
||||
private String bar = "hello"; // XXX: randomize
|
||||
private final Map<String, Object> config = new HashMap<>();
|
||||
private final int foo = 1000; // XXX: randomize
|
||||
private final String bar = "hello"; // XXX: randomize
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
@ -48,41 +46,34 @@ public class ConstructingObjectParserTest {
|
|||
}
|
||||
}
|
||||
|
||||
public static class NestedTest {
|
||||
private final Map<String, Object> config = Collections.singletonMap("foo", Collections.singletonMap("i", 100));
|
||||
|
||||
@Test
|
||||
public void testNested() {
|
||||
NestedExample e = NestedExample.BUILDER.apply(config);
|
||||
assertEquals(100, e.getNested().getI());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class FieldIntegrationTest {
|
||||
private final ConstructingObjectParser<Example> EXAMPLE_BUILDER = new ConstructingObjectParser<>(Example::new);
|
||||
private final ConstructingObjectParser<Path> PATH_BUILDER = new ConstructingObjectParser<Path>(Paths::get, Field.declareString("path"));
|
||||
|
||||
private final Map<String, Object> config = new HashMap<>();
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
check(EXAMPLE_BUILDER.declareFloat("float", Example::setF));
|
||||
check(EXAMPLE_BUILDER.declareInteger("integer", Example::setI));
|
||||
check(EXAMPLE_BUILDER.declareLong("long", Example::setL));
|
||||
check(EXAMPLE_BUILDER.declareDouble("double", Example::setD));
|
||||
check(EXAMPLE_BUILDER.declareBoolean("boolean", Example::setB));
|
||||
check(EXAMPLE_BUILDER.declareString("string", Example::setS));
|
||||
check(EXAMPLE_BUILDER.declareList("stringList", Example::setStringList, ObjectTransforms::transformString));
|
||||
|
||||
// Custom transform (Object => Path)
|
||||
check(EXAMPLE_BUILDER.declareString("path", (example, path) -> example.setP(Paths.get(path))));
|
||||
|
||||
// Custom nested object constructor: { "object": { "path": "some path" } }
|
||||
//check(EXAMPLE_BUILDER.declareObject("object", Example::setP2, PATH_BUILDER));
|
||||
|
||||
config.put("float", 1F);
|
||||
config.put("integer", 1);
|
||||
config.put("long", 1L);
|
||||
config.put("float", 1F);
|
||||
config.put("double", 1D);
|
||||
config.put("long", 1L);
|
||||
config.put("boolean", true);
|
||||
config.put("string", "hello");
|
||||
config.put("stringList", Collections.singletonList("hello"));
|
||||
config.put("list", Collections.singletonList("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsing() {
|
||||
Example e = EXAMPLE_BUILDER.apply(config);
|
||||
FieldExample e = FieldExample.BUILDER.apply(config);
|
||||
assertEquals(1F, e.getF(), 0.1);
|
||||
assertEquals(1D, e.getD(), 0.1);
|
||||
assertEquals(1, e.getI());
|
||||
|
@ -98,40 +89,25 @@ public class ConstructingObjectParserTest {
|
|||
@Test
|
||||
public void testCustomTransform() {
|
||||
config.put("path", "example");
|
||||
Example e = EXAMPLE_BUILDER.apply(config);
|
||||
FieldExample e = FieldExample.BUILDER.apply(config);
|
||||
assertEquals(Paths.get("example"), e.getP());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedObject() {
|
||||
config.put("object", Collections.singletonMap("path", "example"));
|
||||
Example e = EXAMPLE_BUILDER.apply(config);
|
||||
//config.put("object", Collections.singletonMap("path", "example"));
|
||||
//Example e = EXAMPLE_BUILDER.apply(config);
|
||||
//assertEquals(Paths.get("example"), e.getP2());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDuplicateFieldsAreRejected() {
|
||||
// field 'float' is already defined, so this should fail.
|
||||
check(EXAMPLE_BUILDER.declareString("float", (a, b) -> {
|
||||
}));
|
||||
check(FieldExample.BUILDER.declareString("float", (a, b) -> { /*empty*/ }));
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConstructorIntegrationTest {
|
||||
private final ConstructingObjectParser<Example> EXAMPLE_BUILDER = new ConstructingObjectParser<Example>(
|
||||
Example::new,
|
||||
Field.declareInteger("integer"), // arg0
|
||||
Field.declareFloat("float"), // arg0
|
||||
Field.declareLong("long"), // arg2 ...
|
||||
Field.declareDouble("double"),
|
||||
Field.declareBoolean("boolean"),
|
||||
Field.declareString("string"),
|
||||
Field.declareField("path", object -> Paths.get(ObjectTransforms.transformString(object))),
|
||||
Field.declareList("strings", ObjectTransforms::transformString)
|
||||
);
|
||||
|
||||
private final ConstructingObjectParser<Path> PATH_BUILDER = new ConstructingObjectParser<>(Paths::get, Field.declareString("path"));
|
||||
|
||||
private final Map<String, Object> config = new LinkedHashMap<>();
|
||||
|
||||
@Before
|
||||
|
@ -142,14 +118,13 @@ public class ConstructingObjectParserTest {
|
|||
config.put("double", 1D);
|
||||
config.put("boolean", true);
|
||||
config.put("string", "hello");
|
||||
config.put("path", "path1");
|
||||
config.put("object", Collections.singletonMap("path", "path2"));
|
||||
config.put("stringList", Collections.singletonList("hello"));
|
||||
config.put("path", "path");
|
||||
config.put("list", Collections.singletonList("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsing() {
|
||||
Example e = EXAMPLE_BUILDER.apply(config);
|
||||
ConstructorExample e = ConstructorExample.BUILDER.apply(config);
|
||||
assertEquals(1F, e.getF(), 0.1);
|
||||
assertEquals(1D, e.getD(), 0.1);
|
||||
assertEquals(1, e.getI());
|
||||
|
@ -157,23 +132,21 @@ public class ConstructingObjectParserTest {
|
|||
assertEquals(true, e.isB());
|
||||
assertEquals("hello", e.getS());
|
||||
assertEquals(Paths.get("path"), e.getP());
|
||||
|
||||
assertEquals(Collections.singletonList("hello"), e.getStringList());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDuplicateFieldsAreRejected() {
|
||||
// field 'float' is already defined, so this should fail.
|
||||
check(EXAMPLE_BUILDER.declareString("float", (a, b) -> {
|
||||
}));
|
||||
public void testInvalidTypesAreRejected() {
|
||||
config.put("float", "Hello"); // put a string for the float field.
|
||||
ConstructorExample.BUILDER.apply(config); // should fail
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDuplicateConstructorFieldsAreRejected() {
|
||||
String name = "foo";
|
||||
new ConstructingObjectParser<Path>(Paths::get, Field.declareString(name), Field.declareString(name));
|
||||
}
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void testMissingArgumentsAreRejected() {
|
||||
config.remove("path");
|
||||
ConstructorExample.BUILDER.apply(config); // should fail
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
|
@ -229,8 +202,8 @@ public class ConstructingObjectParserTest {
|
|||
}
|
||||
|
||||
public static class DeprecationsAndObsoletes {
|
||||
final ConstructingObjectParser<Example> c = new ConstructingObjectParser<>(Example::new);
|
||||
final BiConsumer<Example, Integer> noOp = (a, b) -> { /* empty */ };
|
||||
final ConstructingObjectParser<ConstructorExample> c = new ConstructingObjectParser<>(ConstructorExample::new);
|
||||
final BiConsumer<ConstructorExample, Integer> noOp = (a, b) -> { /* empty */ };
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
@ -238,7 +211,7 @@ public class ConstructingObjectParserTest {
|
|||
check(c.declareInteger("obsolete", noOp).setObsolete("This setting should cause a failure when someone uses it."));
|
||||
}
|
||||
|
||||
private static class Example {
|
||||
private static class ConstructorExample {
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -246,6 +219,68 @@ public class ConstructingObjectParserTest {
|
|||
// XXX: Implement a custom log appender that captures log4j logs so we can verify the warning is logged.
|
||||
c.apply(Collections.singletonMap("deprecated", 1));
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class ConstructionArguments {
|
||||
static final ConstructingObjectParser<List<Integer>> c0 = /* 0 args */ new ConstructingObjectParser<>(Arrays::<Integer>asList);
|
||||
static final ConstructingObjectParser<List<Integer>> c1 = /* 1 args */ new ConstructingObjectParser<>(Arrays::<Integer>asList, Field.declareInteger("a0"));
|
||||
static final ConstructingObjectParser<List<Integer>> c2 = /* 2 args */ new ConstructingObjectParser<>(Arrays::<Integer>asList, Field.declareInteger("a0"), Field.declareInteger("a1"));
|
||||
static final ConstructingObjectParser<List<Integer>> c3 = /* 3 args */ new ConstructingObjectParser<>(Arrays::<Integer>asList, Field.declareInteger("a0"), Field.declareInteger("a1"), Field.declareInteger("a2"));
|
||||
static final ConstructingObjectParser<List<Integer>> c4 = /* 4 args */ new ConstructingObjectParser<>(Arrays::<Integer>asList, Field.declareInteger("a0"), Field.declareInteger("a1"), Field.declareInteger("a2"), Field.declareInteger("a3"));
|
||||
static final ConstructingObjectParser<List<Integer>> c5 = /* 5 args */ new ConstructingObjectParser<>(Arrays::<Integer>asList, Field.declareInteger("a0"), Field.declareInteger("a1"), Field.declareInteger("a2"), Field.declareInteger("a3"), Field.declareInteger("a4"));
|
||||
static final ConstructingObjectParser<List<Integer>> c6 = /* 6 args */ new ConstructingObjectParser<>(Arrays::<Integer>asList, Field.declareInteger("a0"), Field.declareInteger("a1"), Field.declareInteger("a2"), Field.declareInteger("a3"), Field.declareInteger("a4"), Field.declareInteger("a5"));
|
||||
static final ConstructingObjectParser<List<Integer>> c7 = /* 7 args */ new ConstructingObjectParser<>(Arrays::<Integer>asList, Field.declareInteger("a0"), Field.declareInteger("a1"), Field.declareInteger("a2"), Field.declareInteger("a3"), Field.declareInteger("a4"), Field.declareInteger("a5"), Field.declareInteger("a6"));
|
||||
static final ConstructingObjectParser<List<Integer>> c8 = /* 8 args */ new ConstructingObjectParser<>(Arrays::<Integer>asList, Field.declareInteger("a0"), Field.declareInteger("a1"), Field.declareInteger("a2"), Field.declareInteger("a3"), Field.declareInteger("a4"), Field.declareInteger("a5"), Field.declareInteger("a6"), Field.declareInteger("a7"));
|
||||
static final ConstructingObjectParser<List<Integer>> c9 = /* 9 args */ new ConstructingObjectParser<>(Arrays::<Integer>asList, Field.declareInteger("a0"), Field.declareInteger("a1"), Field.declareInteger("a2"), Field.declareInteger("a3"), Field.declareInteger("a4"), Field.declareInteger("a5"), Field.declareInteger("a6"), Field.declareInteger("a7"), Field.declareInteger("a8"));
|
||||
private final ConstructingObjectParser<List<Integer>> builder;
|
||||
private final int i;
|
||||
|
||||
public ConstructionArguments(ConstructingObjectParser<List<Integer>> builder, int i) {
|
||||
this.builder = builder;
|
||||
this.i = i;
|
||||
}
|
||||
|
||||
static Map<String, Object> genMap(int count) {
|
||||
Map map = new HashMap();
|
||||
for (int i = 0; i < count; i++) {
|
||||
map.put("a" + i, i);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{c0, 0},
|
||||
{c1, 1},
|
||||
{c2, 2},
|
||||
{c3, 3},
|
||||
{c4, 4},
|
||||
{c5, 5},
|
||||
{c6, 6},
|
||||
{c7, 7},
|
||||
{c8, 8},
|
||||
{c9, 9},
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilder() {
|
||||
for (int args = 0; args <= 9; args++) {
|
||||
try {
|
||||
builder.apply(genMap(args));
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (args < i) {
|
||||
fail("Having fewer args than required should not generate an IllegalArgumentException");
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
if (args >= i) {
|
||||
fail("Having enough arguments should not generate a NullPointerException");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
class ConstructorExample {
|
||||
static final ConstructingObjectParser<ConstructorExample> BUILDER = new ConstructingObjectParser<>(
|
||||
ConstructorExample::new,
|
||||
Field.declareInteger("integer"),
|
||||
Field.declareFloat("float"),
|
||||
Field.declareLong("long"),
|
||||
Field.declareDouble("double"),
|
||||
Field.declareBoolean("boolean"),
|
||||
Field.declareString("string"),
|
||||
Field.declareField("path", object -> Paths.get(ObjectTransforms.transformString(object))),
|
||||
Field.declareList("list", ObjectTransforms::transformString)
|
||||
);
|
||||
|
||||
private Path path;
|
||||
private final int i;
|
||||
private final float f;
|
||||
private final double d;
|
||||
private final boolean b;
|
||||
private final long l;
|
||||
private final String s;
|
||||
private final Path p;
|
||||
private final List<String> stringList;
|
||||
|
||||
private ConstructorExample(int i, float f, long l, double d, boolean b, String s, Path p, List<String> stringList) {
|
||||
this.i = i;
|
||||
this.f = f;
|
||||
this.l = l;
|
||||
this.d = d;
|
||||
this.b = b;
|
||||
this.s = s;
|
||||
this.p = p;
|
||||
this.stringList = stringList;
|
||||
}
|
||||
|
||||
int getI() {
|
||||
return i;
|
||||
}
|
||||
|
||||
float getF() {
|
||||
return f;
|
||||
}
|
||||
|
||||
double getD() {
|
||||
return d;
|
||||
}
|
||||
|
||||
boolean isB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
long getL() {
|
||||
return l;
|
||||
}
|
||||
|
||||
String getS() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Path getP() {
|
||||
return p;
|
||||
}
|
||||
|
||||
List<String> getStringList() {
|
||||
return stringList;
|
||||
}
|
||||
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
class Example {
|
||||
private Path path;
|
||||
private int i;
|
||||
private float f;
|
||||
private double d;
|
||||
private boolean b;
|
||||
private long l;
|
||||
private String s;
|
||||
private Path p;
|
||||
private List<String> stringList;
|
||||
|
||||
Example() {
|
||||
}
|
||||
|
||||
Example(int i, float f, long l, double d, boolean b, String s, Path p, List<String> stringList) {
|
||||
this.i = i;
|
||||
this.f = f;
|
||||
this.l = l;
|
||||
this.d = d;
|
||||
this.b = b;
|
||||
this.s = s;
|
||||
this.p = p;
|
||||
this.stringList = stringList;
|
||||
}
|
||||
|
||||
int getI() {
|
||||
return i;
|
||||
}
|
||||
|
||||
void setI(int i) {
|
||||
this.i = i;
|
||||
}
|
||||
|
||||
float getF() {
|
||||
return f;
|
||||
}
|
||||
|
||||
void setF(float f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
double getD() {
|
||||
return d;
|
||||
}
|
||||
|
||||
void setD(double d) {
|
||||
this.d = d;
|
||||
}
|
||||
|
||||
boolean isB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
void setB(boolean b) {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
long getL() {
|
||||
return l;
|
||||
}
|
||||
|
||||
void setL(long l) {
|
||||
this.l = l;
|
||||
}
|
||||
|
||||
String getS() {
|
||||
return s;
|
||||
}
|
||||
|
||||
void setS(String s) {
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
Path getP() {
|
||||
return p;
|
||||
}
|
||||
|
||||
void setP(Path p) {
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
List<String> getStringList() {
|
||||
return stringList;
|
||||
}
|
||||
|
||||
void setStringList(List<String> stringList) {
|
||||
this.stringList = stringList;
|
||||
}
|
||||
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(Path path) {
|
||||
this.path = path;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
class FieldExample {
|
||||
public static final ConstructingObjectParser<FieldExample> BUILDER = new ConstructingObjectParser<>(FieldExample::new);
|
||||
|
||||
static {
|
||||
BUILDER.declareInteger("integer", FieldExample::setI);
|
||||
BUILDER.declareFloat("float", FieldExample::setF);
|
||||
BUILDER.declareDouble("double", FieldExample::setD);
|
||||
BUILDER.declareLong("long", FieldExample::setL);
|
||||
BUILDER.declareBoolean("boolean", FieldExample::setB);
|
||||
BUILDER.declareString("string", FieldExample::setS);
|
||||
BUILDER.declareField("path", FieldExample::setP, (object) -> Paths.get(ObjectTransforms.transformString(object)));
|
||||
BUILDER.declareList("list", FieldExample::setStringList, ObjectTransforms::transformString);
|
||||
}
|
||||
|
||||
private int i;
|
||||
private float f;
|
||||
private double d;
|
||||
private boolean b;
|
||||
private long l;
|
||||
private String s;
|
||||
private Path p;
|
||||
private List<String> stringList;
|
||||
|
||||
public int getI() {
|
||||
return i;
|
||||
}
|
||||
|
||||
private void setI(int i) {
|
||||
this.i = i;
|
||||
}
|
||||
|
||||
float getF() {
|
||||
return f;
|
||||
}
|
||||
|
||||
private void setF(float f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
double getD() {
|
||||
return d;
|
||||
}
|
||||
|
||||
private void setD(double d) {
|
||||
this.d = d;
|
||||
}
|
||||
|
||||
boolean isB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
private void setB(boolean b) {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
long getL() {
|
||||
return l;
|
||||
}
|
||||
|
||||
private void setL(long l) {
|
||||
this.l = l;
|
||||
}
|
||||
|
||||
public String getS() {
|
||||
return s;
|
||||
}
|
||||
|
||||
private void setS(String s) {
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
Path getP() {
|
||||
return p;
|
||||
}
|
||||
|
||||
private void setP(Path p) {
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
List<String> getStringList() {
|
||||
return stringList;
|
||||
}
|
||||
|
||||
private void setStringList(List<String> stringList) {
|
||||
this.stringList = stringList;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
class MixedExample {
|
||||
static final ConstructingObjectParser<MixedExample> BUILDER = new ConstructingObjectParser<MixedExample>(MixedExample::new, Field.declareInteger("foo"));
|
||||
static final ConstructingObjectParser<MixedExample> BUILDER = new ConstructingObjectParser<>(MixedExample::new, Field.declareInteger("foo"));
|
||||
|
||||
static {
|
||||
BUILDER.declareString("bar", MixedExample::setBar);
|
||||
|
@ -10,7 +10,7 @@ class MixedExample {
|
|||
private int foo;
|
||||
private String bar;
|
||||
|
||||
public MixedExample(int foo) {
|
||||
private MixedExample(int foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ class MixedExample {
|
|||
return bar;
|
||||
}
|
||||
|
||||
public void setBar(String bar) {
|
||||
private void setBar(String bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
public class NestedExample {
|
||||
static final ConstructingObjectParser<NestedExample> BUILDER = new ConstructingObjectParser<>(
|
||||
NestedExample::new,
|
||||
Field.declareObject("foo", Nested.BUILDER)
|
||||
);
|
||||
private final Nested nested;
|
||||
|
||||
private NestedExample(Nested nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
public Nested getNested() {
|
||||
return nested;
|
||||
}
|
||||
|
||||
public static class Nested {
|
||||
static final ConstructingObjectParser<Nested> BUILDER = new ConstructingObjectParser<>(Nested::new, Field.declareInteger("i"));
|
||||
|
||||
private final int i;
|
||||
|
||||
Nested(int i) {
|
||||
this.i = i;
|
||||
}
|
||||
|
||||
public int getI() {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -7,35 +7,39 @@ import org.logstash.common.parser.ObjectTransforms;
|
|||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
class TranslateFilterPlugin {
|
||||
final static ConstructingObjectParser<TranslateFilter> TRANSLATE = new ConstructingObjectParser<TranslateFilter>(
|
||||
TranslateFilterPlugin::newFilter,
|
||||
Field.declareString("field"),
|
||||
private final static ConstructingObjectParser<TranslateFilter> TRANSLATE_MAP = new ConstructingObjectParser<>(
|
||||
TranslateFilter::new, Field.declareString("field"), Field.declareMap("dictionary"));
|
||||
private final static ConstructingObjectParser<FileBackedTranslateFilter> TRANSLATE_FILE = new ConstructingObjectParser<>(
|
||||
FileBackedTranslateFilter::new, Field.declareString("field"), Field.declareField("dictionary_path", TranslateFilterPlugin::parsePath));
|
||||
|
||||
// Things get a bit weird here. I can explain. The translate filter has two modes of operation:
|
||||
// One, where the user specifies a 'dictionary'. Two, where the user specifies 'dictionary_path'.
|
||||
// These two modes are mutually exclusive, so it makes sense to have a custom builder method to
|
||||
// determine which mode to use based on the given arguments. See TranslateFilter.newFilter.
|
||||
// It's unclear if this modality is a desirable feature, but I am only porting the translate filter
|
||||
// in this scenario as a a way to demonstrate and test the capabilities of ConstructingObjectParser.
|
||||
Field.declareMap("dictionary"),
|
||||
Field.declareField("dictionary_path", TranslateFilterPlugin::parsePath)
|
||||
);
|
||||
/**
|
||||
* The translate filter (as written in Ruby) is currently bimodal. There are two modes:
|
||||
* <p>
|
||||
* 1) A hand-written dictionary `dictionary => { "key" => value, ... }
|
||||
* 2) A file-backed dictionary `dictionary_path => "/some/path.yml"`
|
||||
* <p>
|
||||
* Because of these, provide a custom Function to call the correct implementation (file or static)
|
||||
*/
|
||||
final static Function<Map<String, Object>, TranslateFilter> BUILDER = TranslateFilterPlugin::newFilter;
|
||||
|
||||
static {
|
||||
// These are all nice settings that are optional in the plugin.
|
||||
TRANSLATE.declareString("destination", TranslateFilter::setDestination);
|
||||
TRANSLATE.declareBoolean("exact", TranslateFilter::setExact);
|
||||
TRANSLATE.declareBoolean("override", TranslateFilter::setOverride);
|
||||
TRANSLATE.declareBoolean("regex", TranslateFilter::setRegex);
|
||||
TRANSLATE.declareString("fallback", TranslateFilter::setFallback);
|
||||
// Apply most settings to both Translate Filter modes.
|
||||
for (ConstructingObjectParser<? extends TranslateFilter> builder : Arrays.asList(TRANSLATE_MAP, TRANSLATE_FILE)) {
|
||||
builder.declareString("destination", TranslateFilter::setDestination);
|
||||
builder.declareBoolean("exact", TranslateFilter::setExact);
|
||||
builder.declareBoolean("override", TranslateFilter::setOverride);
|
||||
builder.declareBoolean("regex", TranslateFilter::setRegex);
|
||||
builder.declareString("fallback", TranslateFilter::setFallback);
|
||||
}
|
||||
|
||||
// Special handling of refresh_interval to reject when dictionary_path is not also set.
|
||||
TRANSLATE.declareInteger("refresh_interval", TranslateFilterPlugin::setRefreshInterval);
|
||||
// File-backed mode gets a special `refresh_interval` setting.
|
||||
TRANSLATE_FILE.declareInteger("refresh_interval", FileBackedTranslateFilter::setRefreshInterval);
|
||||
}
|
||||
|
||||
private static Path parsePath(Object input) {
|
||||
|
@ -48,34 +52,26 @@ class TranslateFilterPlugin {
|
|||
return path;
|
||||
}
|
||||
|
||||
private static TranslateFilter newFilter(String source, Map<String, Object> map, Path path) {
|
||||
if (map != null && path != null) {
|
||||
private static TranslateFilter newFilter(Map<String, Object> config) {
|
||||
if (config.containsKey("dictionary") && config.containsKey("dictionary_path")) {
|
||||
throw new IllegalArgumentException("You must specify either dictionary or dictionary_path, not both. Both are set.");
|
||||
}
|
||||
|
||||
if (map != null) {
|
||||
if (config.containsKey("dictionary")) {
|
||||
// "dictionary" field was set, so args[1] is a map.
|
||||
return new TranslateFilter(source, map);
|
||||
return TRANSLATE_MAP.apply(config);
|
||||
} else {
|
||||
// dictionary_path set, so let's use a file-backed translate filter.
|
||||
return new FileBackedTranslateFilter(source, path);
|
||||
return TRANSLATE_FILE.apply(config);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setRefreshInterval(TranslateFilter filter, int refresh) {
|
||||
if (filter instanceof FileBackedTranslateFilter) {
|
||||
((FileBackedTranslateFilter) filter).setRefreshInterval(refresh);
|
||||
} else {
|
||||
throw new IllegalArgumentException("refresh_interval is only valid when using dictionary_path.");
|
||||
}
|
||||
}
|
||||
|
||||
// Processor will be defined in another PR, so this exists as a placeholder;
|
||||
interface Processor extends Function<Collection<Event>, Collection<Event>> {
|
||||
// Processor interface will be defined in another PR, so this exists as a placeholder;
|
||||
private interface Processor extends Function<Collection<Event>, Collection<Event>> {
|
||||
}
|
||||
|
||||
public static class TranslateFilter implements Processor {
|
||||
protected Map<String, Object> map;
|
||||
Map<String, Object> map;
|
||||
private String source;
|
||||
private String target = "translation";
|
||||
private String fallback;
|
||||
|
@ -142,7 +138,7 @@ class TranslateFilterPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
public static class FileBackedTranslateFilter extends TranslateFilter {
|
||||
static class FileBackedTranslateFilter extends TranslateFilter {
|
||||
private final Path path;
|
||||
private int refresh;
|
||||
|
||||
|
|
|
@ -13,6 +13,6 @@ public class TranslateFilterPluginTest {
|
|||
config.put("dictionary", Collections.emptyMap());
|
||||
config.put("field", "fancy");
|
||||
|
||||
TranslateFilterPlugin.TranslateFilter filter = TranslateFilterPlugin.TRANSLATE.apply(config);
|
||||
TranslateFilterPlugin.TranslateFilter filter = TranslateFilterPlugin.BUILDER.apply(config);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue