mirror of
https://github.com/elastic/logstash.git
synced 2025-04-24 14:47:19 -04:00
Move to org.logstash.common.parser
Previously this was in org.logstash.plugin, and it felt not the right package. Big code reorg also: * refactored all the shorthand helpers like declareInteger into default methods of a new ObjectParser interface. * moved object transforms like transformInteger to a new ObjectTransforms class
This commit is contained in:
parent
2bf1b4aed6
commit
603c0869f4
10 changed files with 560 additions and 548 deletions
|
@ -0,0 +1,154 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A functional class which constructs an object from a given configuration map.
|
||||
* <p>
|
||||
* History: This is idea is taken largely from Elasticsearch's ConstructingObjectParser
|
||||
*
|
||||
* @param <Value> The object type to construct when `parse` is called.
|
||||
*/
|
||||
public class ConstructingObjectParser<Value> implements ObjectParser<Value> {
|
||||
private final Logger logger = LogManager.getLogger();
|
||||
private final Function<Object[], Value> builder;
|
||||
private final Map<String, FieldDefinition<Value>> parsers = new LinkedHashMap<>();
|
||||
private final Map<String, FieldDefinition<Object[]>> constructorArgs;
|
||||
|
||||
/**
|
||||
* @param supplier The supplier which produces an object instance.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public ConstructingObjectParser(Supplier<Value> supplier) {
|
||||
this.builder = args -> supplier.get();
|
||||
|
||||
// Reject any attempts to add constructor fields with an immutable map.
|
||||
constructorArgs = Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
this.builder = builder;
|
||||
constructorArgs = new TreeMap<>();
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public <T> Field declareField(String name, BiConsumer<Value, T> consumer, Function<Object, T> transform) {
|
||||
if (isKnownField(name)) {
|
||||
throw new IllegalArgumentException("Duplicate field defined '" + name + "'");
|
||||
}
|
||||
|
||||
BiConsumer<Value, Object> objConsumer = (value, object) -> consumer.accept(value, transform.apply(object));
|
||||
FieldDefinition<Value> field = new FieldDefinition<>(objConsumer, FieldUsage.Field);
|
||||
parsers.put(name, field);
|
||||
return field;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public <T> Field declareConstructorArg(String name, Function<Object, T> transform) {
|
||||
if (isKnownField(name)) {
|
||||
throw new IllegalArgumentException("Duplicate field defined '" + name + "'");
|
||||
}
|
||||
|
||||
final int position = constructorArgs.size();
|
||||
BiConsumer<Object[], Object> objConsumer = (array, object) -> array[position] = transform.apply(object);
|
||||
FieldDefinition<Object[]> field = new FieldDefinition<>(objConsumer, FieldUsage.Constructor);
|
||||
try {
|
||||
constructorArgs.put(name, field);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// This will be thrown when this ConstructingObjectParser is created with a Supplier (which takes no arguments)
|
||||
// for example, new ConstructingObjectParser<>((Supplier<String>) String::new)
|
||||
throw new UnsupportedOperationException("Cannot add constructor args because the constructor doesn't take any arguments!");
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an object using the given config.
|
||||
* <p>
|
||||
* The intent is that a config map, such as one from a Logstash pipeline config:
|
||||
* <p>
|
||||
* input {
|
||||
* example {
|
||||
* some => "setting"
|
||||
* goes => "here"
|
||||
* }
|
||||
* }
|
||||
* <p>
|
||||
* ... will know how to build an object for the above "example" input plugin.
|
||||
*/
|
||||
public Value apply(Map<String, Object> config) {
|
||||
rejectUnknownFields(config.keySet());
|
||||
|
||||
Value value = construct(config);
|
||||
|
||||
// Now call all the object setters/etc
|
||||
for (Map.Entry<String, Object> entry : config.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
if (constructorArgs.containsKey(name)) {
|
||||
// Skip constructor arguments
|
||||
continue;
|
||||
}
|
||||
|
||||
FieldDefinition<Value> field = parsers.get(name);
|
||||
assert field != null;
|
||||
|
||||
try {
|
||||
field.accept(value, entry.getValue());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Field " + name + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private void rejectUnknownFields(Set<String> configNames) throws IllegalArgumentException {
|
||||
// Check for any unknown parameters.
|
||||
List<String> unknown = configNames.stream().filter(name -> !isKnownField(name)).collect(Collectors.toList());
|
||||
|
||||
if (!unknown.isEmpty()) {
|
||||
throw new IllegalArgumentException("Unknown settings: " + unknown);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isKnownField(String name) {
|
||||
return (parsers.containsKey(name) || constructorArgs.containsKey(name));
|
||||
}
|
||||
|
||||
private Value construct(Map<String, Object> config) throws IllegalArgumentException {
|
||||
// XXX: Maybe this can just be an Object[]
|
||||
Object[] args = new Object[constructorArgs.size()];
|
||||
|
||||
// Constructor arguments. Any constructor argument is a *required* setting.
|
||||
for (Map.Entry<String, FieldDefinition<Object[]>> argInfo : constructorArgs.entrySet()) {
|
||||
String name = argInfo.getKey();
|
||||
FieldDefinition<Object[]> field = argInfo.getValue();
|
||||
|
||||
if (config.containsKey(name)) {
|
||||
if (field.isObsolete()) {
|
||||
throw new IllegalArgumentException("Field '" + name + "' is obsolete and may not be used. " + field.getDetails());
|
||||
} else if (field.isDeprecated()) {
|
||||
logger.warn("Field '" + name + "' is deprecated and should be avoided. " + field.getDetails());
|
||||
}
|
||||
|
||||
field.accept(args, config.get(name));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Missing required argument '" + name);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.apply(args);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.logstash.plugin;
|
||||
package org.logstash.common.parser;
|
||||
|
||||
public interface Field {
|
||||
Field setDeprecated(String details);
|
|
@ -1,4 +1,4 @@
|
|||
package org.logstash.plugin;
|
||||
package org.logstash.common.parser;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.logstash.plugin;
|
||||
package org.logstash.common.parser;
|
||||
|
||||
enum FieldStatus {
|
||||
Supported,
|
|
@ -1,4 +1,4 @@
|
|||
package org.logstash.plugin;
|
||||
package org.logstash.common.parser;
|
||||
|
||||
public enum FieldUsage {
|
||||
Constructor,
|
|
@ -0,0 +1,145 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface ObjectParser<Value> extends Function<Map<String, Object>, Value> {
|
||||
<T> Field declareField(String name, BiConsumer<Value, T> consumer, Function<Object, T> transform);
|
||||
|
||||
<T> Field declareConstructorArg(String name, Function<Object, T> transform);
|
||||
|
||||
/**
|
||||
* Add an field with an long value.
|
||||
*
|
||||
* @param name the name of this field
|
||||
* @param consumer the function to call once the value is available
|
||||
*/
|
||||
default Field declareLong(String name, BiConsumer<Value, Long> consumer) {
|
||||
return declareField(name, consumer, ObjectTransforms::transformLong);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare an long constructor argument.
|
||||
*
|
||||
* @param name the name of the field.
|
||||
*/
|
||||
default Field declareLong(String name) {
|
||||
return declareConstructorArg(name, ObjectTransforms::transformLong);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an field with an integer value.
|
||||
*
|
||||
* @param name the name of this field
|
||||
* @param consumer the function to call once the value is available
|
||||
*/
|
||||
default Field declareInteger(String name, BiConsumer<Value, Integer> consumer) {
|
||||
return declareField(name, consumer, ObjectTransforms::transformInteger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare an integer constructor argument.
|
||||
*
|
||||
* @param name the name of the field.
|
||||
*/
|
||||
default Field declareInteger(String name) {
|
||||
return declareConstructorArg(name, ObjectTransforms::transformInteger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a field with a string value.
|
||||
*
|
||||
* @param name the name of this field
|
||||
* @param consumer the function to call once the value is available
|
||||
*/
|
||||
default Field declareString(String name, BiConsumer<Value, String> consumer) {
|
||||
return declareField(name, consumer, ObjectTransforms::transformString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a constructor argument that is a string.
|
||||
*
|
||||
* @param name the name of this field.
|
||||
*/
|
||||
default Field declareString(String name) {
|
||||
return declareConstructorArg(name, ObjectTransforms::transformString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a field with a List containing T instances
|
||||
*
|
||||
* @param name the name of this field
|
||||
* @param consumer the consumer to call when this field is processed
|
||||
* @param transform the function for transforming Object to T types
|
||||
* @param <T> the type stored in the List.
|
||||
*/
|
||||
default <T> Field declareList(String name, BiConsumer<Value, List<T>> consumer, Function<Object, T> transform) {
|
||||
return declareField(name, consumer, object -> ObjectTransforms.transformList(object, transform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a constructor argument which is a List
|
||||
*
|
||||
* @param name The name of the argument.
|
||||
* @param transform The object -> T transform function
|
||||
* @param <T> The type of object contained in the list.
|
||||
*/
|
||||
default <T> Field declareList(String name, Function<Object, T> transform) {
|
||||
return declareConstructorArg(name, (object) -> ObjectTransforms.transformList(object, transform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a constructor argument that is a float.
|
||||
*
|
||||
* @param name the name of the argument
|
||||
*/
|
||||
default Field declareFloat(String name) {
|
||||
return declareConstructorArg(name, ObjectTransforms::transformFloat);
|
||||
}
|
||||
|
||||
default Field declareFloat(String name, BiConsumer<Value, Float> consumer) {
|
||||
return declareField(name, consumer, ObjectTransforms::transformFloat);
|
||||
}
|
||||
|
||||
default Field declareDouble(String name) {
|
||||
return declareConstructorArg(name, ObjectTransforms::transformDouble);
|
||||
}
|
||||
|
||||
default Field declareDouble(String name, BiConsumer<Value, Double> consumer) {
|
||||
return declareField(name, consumer, ObjectTransforms::transformDouble);
|
||||
}
|
||||
|
||||
default Field declareBoolean(String name) {
|
||||
return declareConstructorArg(name, ObjectTransforms::transformBoolean);
|
||||
}
|
||||
|
||||
default Field declareBoolean(String name, BiConsumer<Value, Boolean> consumer) {
|
||||
return declareField(name, consumer, ObjectTransforms::transformBoolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a field with an object value
|
||||
*
|
||||
* @param name the name of this field
|
||||
* @param consumer the function to call once the value is available
|
||||
* @param parser The ConstructingObjectParser that will build the object
|
||||
* @param <T> The type of object to store as the value.
|
||||
*/
|
||||
default <T> Field declareObject(String name, BiConsumer<Value, T> consumer, ConstructingObjectParser<T> parser) {
|
||||
return declareField(name, consumer, (t) -> ObjectTransforms.transformObject(t, parser));
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a constructor argument that is an object.
|
||||
*
|
||||
* @param name the name of the field which represents this constructor argument
|
||||
* @param parser the ConstructingObjectParser that builds the object
|
||||
* @param <T> The type of object created by the parser.
|
||||
*/
|
||||
default <T> Field declareObject(String name, ConstructingObjectParser<T> parser) {
|
||||
return declareConstructorArg(name, (t) -> ObjectTransforms.transformObject(t, parser));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package org.logstash.common.parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
class ObjectTransforms {
|
||||
/**
|
||||
* A function which takes an Object and returns an Integer
|
||||
*
|
||||
* @param object the object to transform to Integer
|
||||
* @return An Integer based on the given object.
|
||||
* @throws IllegalArgumentException if conversion is not possible
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static Integer transformInteger(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof Number) {
|
||||
return ((Number) object).intValue();
|
||||
} else if (object instanceof String) {
|
||||
return Integer.parseInt((String) object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a number, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static Float transformFloat(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof Number) {
|
||||
return ((Number) object).floatValue();
|
||||
} else if (object instanceof String) {
|
||||
return Float.parseFloat((String) object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a number, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static Double transformDouble(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof Number) {
|
||||
return ((Number) object).doubleValue();
|
||||
} else if (object instanceof String) {
|
||||
return Double.parseDouble((String) object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a number, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static Long transformLong(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof Number) {
|
||||
return ((Number) object).longValue();
|
||||
} else if (object instanceof String) {
|
||||
return Long.parseLong((String) object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a number, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static String transformString(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof String) {
|
||||
return (String) object;
|
||||
} else if (object instanceof Number) {
|
||||
return object.toString();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a string, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static Boolean transformBoolean(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof Boolean) {
|
||||
return (Boolean) object;
|
||||
} else if (object instanceof String) {
|
||||
switch ((String) object) {
|
||||
case "true":
|
||||
return true;
|
||||
case "false":
|
||||
return false;
|
||||
default:
|
||||
throw new IllegalArgumentException("Value must be a boolean 'true' or 'false', but is " + object);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a boolean, but is " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static <T> T transformObject(Object object, ConstructingObjectParser<T> parser) throws IllegalArgumentException {
|
||||
if (object instanceof Map) {
|
||||
// XXX: Fix this unchecked cast.
|
||||
return parser.apply((Map<String, Object>) object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Object value must be a Map, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static <T> List<T> transformList(Object object, Function<Object, T> transform) throws IllegalArgumentException {
|
||||
// XXX: Support Iterator?
|
||||
if (object instanceof List) {
|
||||
List<Object> list = (List<Object>) object;
|
||||
List<T> result = new ArrayList<>(list.size());
|
||||
list.stream().map(transform).forEach(result::add);
|
||||
return result;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Object value must be a List, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,395 +0,0 @@
|
|||
package org.logstash.plugin;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A functional class which constructs an object from a given configuration map.
|
||||
*
|
||||
* History: This is idea is taken largely from Elasticsearch's ConstructingObjectParser
|
||||
*
|
||||
* @param <Value> The object type to construct when `parse` is called.
|
||||
*/
|
||||
public class ConstructingObjectParser<Value> implements Function<Map<String, Object>, Value> {
|
||||
private final Logger logger = LogManager.getLogger();
|
||||
private final Function<Object[], Value> builder;
|
||||
private final Map<String, FieldDefinition<Value>> parsers = new LinkedHashMap<>();
|
||||
private final Map<String, FieldDefinition<Object[]>> constructorArgs;
|
||||
|
||||
/**
|
||||
* @param supplier The supplier which produces an object instance.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public ConstructingObjectParser(Supplier<Value> supplier) {
|
||||
this.builder = args -> supplier.get();
|
||||
|
||||
// Reject any attempts to add constructor fields with an immutable map.
|
||||
constructorArgs = Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
this.builder = builder;
|
||||
constructorArgs = new TreeMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* A function which takes an Object and returns an Integer
|
||||
*
|
||||
* @param object the object to transform to Integer
|
||||
* @return An Integer based on the given object.
|
||||
* @throws IllegalArgumentException if conversion is not possible
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static Integer transformInteger(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof Number) {
|
||||
return ((Number) object).intValue();
|
||||
} else if (object instanceof String) {
|
||||
return Integer.parseInt((String) object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a number, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static Float transformFloat(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof Number) {
|
||||
return ((Number) object).floatValue();
|
||||
} else if (object instanceof String) {
|
||||
return Float.parseFloat((String) object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a number, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static Double transformDouble(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof Number) {
|
||||
return ((Number) object).doubleValue();
|
||||
} else if (object instanceof String) {
|
||||
return Double.parseDouble((String) object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a number, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static Long transformLong(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof Number) {
|
||||
return ((Number) object).longValue();
|
||||
} else if (object instanceof String) {
|
||||
return Long.parseLong((String) object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a number, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static String transformString(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof String) {
|
||||
return (String) object;
|
||||
} else if (object instanceof Number) {
|
||||
return object.toString();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a string, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static Boolean transformBoolean(Object object) throws IllegalArgumentException {
|
||||
if (object instanceof Boolean) {
|
||||
return (Boolean) object;
|
||||
} else if (object instanceof String) {
|
||||
switch ((String) object) {
|
||||
case "true":
|
||||
return true;
|
||||
case "false":
|
||||
return false;
|
||||
default:
|
||||
throw new IllegalArgumentException("Value must be a boolean 'true' or 'false', but is " + object);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Value must be a boolean, but is " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static <T> T transformObject(Object object, ConstructingObjectParser<T> parser) throws IllegalArgumentException {
|
||||
if (object instanceof Map) {
|
||||
// XXX: Fix this unchecked cast.
|
||||
return parser.apply((Map<String, Object>) object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Object value must be a Map, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public static <T> List<T> transformList(Object object, Function<Object, T> transform) throws IllegalArgumentException {
|
||||
// XXX: Support Iterator?
|
||||
if (object instanceof List) {
|
||||
List<Object> list = (List<Object>) object;
|
||||
List<T> result = new ArrayList<>(list.size());
|
||||
list.stream().map(transform).forEach(result::add);
|
||||
return result;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Object value must be a List, but is a " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an field with an long value.
|
||||
*
|
||||
* @param name the name of this field
|
||||
* @param consumer the function to call once the value is available
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareLong(String name, BiConsumer<Value, Long> consumer) {
|
||||
return declareField(name, consumer, ConstructingObjectParser::transformLong);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare an long constructor argument.
|
||||
*
|
||||
* @param name the name of the field.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareLong(String name) {
|
||||
return declareConstructorArg(name, ConstructingObjectParser::transformLong);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public <T> Field declareField(String name, BiConsumer<Value, T> consumer, Function<Object, T> transform) {
|
||||
BiConsumer<Value, Object> objConsumer = (value, object) -> consumer.accept(value, transform.apply(object));
|
||||
FieldDefinition<Value> field = new FieldDefinition<>(objConsumer, FieldUsage.Field);
|
||||
parsers.put(name, field);
|
||||
return field;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public <T> Field declareConstructorArg(String name, Function<Object, T> transform) {
|
||||
final int position = constructorArgs.size();
|
||||
BiConsumer<Object[], Object> objConsumer = (array, object) -> array[position] = transform.apply(object);
|
||||
FieldDefinition<Object[]> field = new FieldDefinition<>(objConsumer, FieldUsage.Constructor);
|
||||
try {
|
||||
constructorArgs.put(name, field);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// This will be thrown when this ConstructingObjectParser is created with a Supplier (which takes no arguments)
|
||||
// for example, new ConstructingObjectParser<>((Supplier<String>) String::new)
|
||||
throw new UnsupportedOperationException("Cannot add constructor args because the constructor doesn't take any arguments!");
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an field with an integer value.
|
||||
*
|
||||
* @param name the name of this field
|
||||
* @param consumer the function to call once the value is available
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareInteger(String name, BiConsumer<Value, Integer> consumer) {
|
||||
return declareField(name, consumer, ConstructingObjectParser::transformInteger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare an integer constructor argument.
|
||||
*
|
||||
* @param name the name of the field.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareInteger(String name) {
|
||||
return declareConstructorArg(name, ConstructingObjectParser::transformInteger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a field with a string value.
|
||||
*
|
||||
* @param name the name of this field
|
||||
* @param consumer the function to call once the value is available
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareString(String name, BiConsumer<Value, String> consumer) {
|
||||
return declareField(name, consumer, ConstructingObjectParser::transformString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a constructor argument that is a string.
|
||||
*
|
||||
* @param name the name of this field.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareString(String name) {
|
||||
return declareConstructorArg(name, ConstructingObjectParser::transformString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a field with a List containing T instances
|
||||
* @param name the name of this field
|
||||
* @param consumer the consumer to call when this field is processed
|
||||
* @param transform the function for transforming Object to T types
|
||||
* @param <T> the type stored in the List.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public <T> Field declareList(String name, BiConsumer<Value, List<T>> consumer, Function<Object, T> transform) {
|
||||
return declareField(name, consumer, object -> transformList(object, transform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a constructor argument which is a List
|
||||
*
|
||||
* @param name The name of the argument.
|
||||
* @param transform The object -> T transform function
|
||||
* @param <T> The type of object contained in the list.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public <T> Field declareList(String name, Function<Object, T> transform) {
|
||||
return declareConstructorArg(name, (object) -> transformList(object, transform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a constructor argument that is a float.
|
||||
*
|
||||
* @param name the name of the argument
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareFloat(String name) {
|
||||
return declareConstructorArg(name, ConstructingObjectParser::transformFloat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareFloat(String name, BiConsumer<Value, Float> consumer) {
|
||||
return declareField(name, consumer, ConstructingObjectParser::transformFloat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareDouble(String name) {
|
||||
return declareConstructorArg(name, ConstructingObjectParser::transformDouble);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareDouble(String name, BiConsumer<Value, Double> consumer) {
|
||||
return declareField(name, consumer, ConstructingObjectParser::transformDouble);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareBoolean(String name) {
|
||||
return declareConstructorArg(name, ConstructingObjectParser::transformBoolean);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public Field declareBoolean(String name, BiConsumer<Value, Boolean> consumer) {
|
||||
return declareField(name, consumer, ConstructingObjectParser::transformBoolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a field with an object value
|
||||
*
|
||||
* @param name the name of this field
|
||||
* @param consumer the function to call once the value is available
|
||||
* @param parser The ConstructingObjectParser that will build the object
|
||||
* @param <T> The type of object to store as the value.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public <T> Field declareObject(String name, BiConsumer<Value, T> consumer, ConstructingObjectParser<T> parser) {
|
||||
return declareField(name, consumer, (t) -> transformObject(t, parser));
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a constructor argument that is an object.
|
||||
*
|
||||
* @param name the name of the field which represents this constructor argument
|
||||
* @param parser the ConstructingObjectParser that builds the object
|
||||
* @param <T> The type of object created by the parser.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess") // Public Interface
|
||||
public <T> Field declareObject(String name, ConstructingObjectParser<T> parser) {
|
||||
return declareConstructorArg(name, (t) -> transformObject(t, parser));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an object using the given config.
|
||||
*
|
||||
* The intent is that a config map, such as one from a Logstash pipeline config:
|
||||
*
|
||||
* input {
|
||||
* example {
|
||||
* some => "setting"
|
||||
* goes => "here"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ... will know how to build an object for the above "example" input plugin.
|
||||
*/
|
||||
public Value apply(Map<String, Object> config) {
|
||||
rejectUnknownFields(config.keySet());
|
||||
|
||||
Value value = construct(config);
|
||||
|
||||
// Now call all the object setters/etc
|
||||
for (Map.Entry<String, Object> entry : config.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
if (constructorArgs.containsKey(name)) {
|
||||
// Skip constructor arguments
|
||||
continue;
|
||||
}
|
||||
|
||||
FieldDefinition<Value> field = parsers.get(name);
|
||||
assert field != null;
|
||||
|
||||
try {
|
||||
field.accept(value, entry.getValue());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Field " + name + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private void rejectUnknownFields(Set<String> configNames) throws IllegalArgumentException {
|
||||
// Check for any unknown parameters.
|
||||
List<String> unknown = configNames.stream().filter(name -> !isKnownField(name)).collect(Collectors.toList());
|
||||
|
||||
if (!unknown.isEmpty()) {
|
||||
throw new IllegalArgumentException("Unknown settings: " + unknown);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isKnownField(String name) {
|
||||
return (parsers.containsKey(name) || constructorArgs.containsKey(name));
|
||||
}
|
||||
|
||||
private Value construct(Map<String, Object> config) throws IllegalArgumentException {
|
||||
// XXX: Maybe this can just be an Object[]
|
||||
Object[] args = new Object[constructorArgs.size()];
|
||||
|
||||
// Constructor arguments. Any constructor argument is a *required* setting.
|
||||
for (Map.Entry<String, FieldDefinition<Object[]>> argInfo : constructorArgs.entrySet()) {
|
||||
String name = argInfo.getKey();
|
||||
FieldDefinition<Object[]> field = argInfo.getValue();
|
||||
|
||||
if (config.containsKey(name)) {
|
||||
if (field.isObsolete()) {
|
||||
throw new IllegalArgumentException("Field '" + name + "' is obsolete and may not be used. " + field.getDetails());
|
||||
} else if (field.isDeprecated()) {
|
||||
logger.warn("Field '" + name + "' is deprecated and should be avoided. " + field.getDetails());
|
||||
}
|
||||
|
||||
field.accept(args, config.get(name));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Missing required argument '" + name + "' for " + getClass());
|
||||
}
|
||||
}
|
||||
|
||||
return builder.apply(args);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.logstash.plugin;
|
||||
package org.logstash.common.parser;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -11,6 +11,7 @@ import java.nio.file.Paths;
|
|||
import java.util.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
|
@ -36,13 +37,13 @@ public class ConstructingObjectParserTest {
|
|||
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, ConstructingObjectParser::transformString));
|
||||
check(EXAMPLE_BUILDER.declareList("stringList", Example::setStringList, ObjectTransforms::transformString));
|
||||
|
||||
// Custom transform (Object => Path)
|
||||
check(EXAMPLE_BUILDER.declareString("path", (example, path) -> example.setPath(Paths.get(path))));
|
||||
check(EXAMPLE_BUILDER.declareString("path", (example, path) -> example.setP1(Paths.get(path))));
|
||||
|
||||
// Custom nested object constructor: { "object": { "path": "some path" } }
|
||||
check(EXAMPLE_BUILDER.declareObject("object", Example::setPath, PATH_BUILDER));
|
||||
check(EXAMPLE_BUILDER.declareObject("object", Example::setP2, PATH_BUILDER));
|
||||
|
||||
config.put("float", 1F);
|
||||
config.put("integer", 1);
|
||||
|
@ -63,20 +64,24 @@ public class ConstructingObjectParserTest {
|
|||
assertEquals(true, e.isB());
|
||||
assertEquals("hello", e.getS());
|
||||
assertEquals(Collections.singletonList("hello"), e.getStringList());
|
||||
|
||||
// because they are not set and the default in the Example class is null.
|
||||
assertNull(e.getP1());
|
||||
assertNull(e.getP2());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomTransform() {
|
||||
config.put("path", "example");
|
||||
Example e = EXAMPLE_BUILDER.apply(config);
|
||||
assertEquals(Paths.get("example"), e.getPath());
|
||||
assertEquals(Paths.get("example"), e.getP1());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedObject() {
|
||||
config.put("object", Collections.singletonMap("path", "example"));
|
||||
Example e = EXAMPLE_BUILDER.apply(config);
|
||||
assertEquals(Paths.get("example"), e.getPath());
|
||||
assertEquals(Paths.get("example"), e.getP2());
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
|
@ -86,82 +91,11 @@ public class ConstructingObjectParserTest {
|
|||
check(EXAMPLE_BUILDER.declareString("invalid"));
|
||||
}
|
||||
|
||||
private static class Example {
|
||||
private int i;
|
||||
private float f;
|
||||
private double d;
|
||||
private boolean b;
|
||||
|
||||
private long l;
|
||||
private String s;
|
||||
|
||||
private List<String> stringList;
|
||||
private Path path;
|
||||
|
||||
List<String> getStringList() {
|
||||
return stringList;
|
||||
}
|
||||
|
||||
void setStringList(List<String> stringList) {
|
||||
this.stringList = stringList;
|
||||
}
|
||||
|
||||
Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
void setPath(Path path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
long getL() {
|
||||
return l;
|
||||
}
|
||||
|
||||
void setL(long l) {
|
||||
this.l = l;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
String getS() {
|
||||
return s;
|
||||
}
|
||||
|
||||
void setS(String s) {
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDuplicateFieldsAreRejected() {
|
||||
// field 'float' is already defined, so this should fail.
|
||||
check(EXAMPLE_BUILDER.declareString("float", (a, b) -> {
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,7 +122,7 @@ public class ConstructingObjectParserTest {
|
|||
// Custom nested object constructor: { "object": { "path": "some path" } }
|
||||
check(EXAMPLE_BUILDER.declareObject("object", PATH_BUILDER));
|
||||
|
||||
check(EXAMPLE_BUILDER.declareList("stringList", ConstructingObjectParser::transformString));
|
||||
check(EXAMPLE_BUILDER.declareList("stringList", ObjectTransforms::transformString));
|
||||
|
||||
config.put("float", 1F);
|
||||
config.put("integer", 1);
|
||||
|
@ -216,68 +150,19 @@ public class ConstructingObjectParserTest {
|
|||
assertEquals(Collections.singletonList("hello"), e.getStringList());
|
||||
}
|
||||
|
||||
private static class Example {
|
||||
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 p1;
|
||||
private final Path p2;
|
||||
|
||||
private final List<String> stringList;
|
||||
|
||||
Example(int i, float f, long l, double d, boolean b, String s, Path p1, Path p2, List<String> stringList) {
|
||||
this.i = i;
|
||||
this.f = f;
|
||||
this.l = l;
|
||||
this.d = d;
|
||||
this.b = b;
|
||||
this.s = s;
|
||||
this.p1 = p1;
|
||||
this.p2 = p2;
|
||||
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 getP1() {
|
||||
return p1;
|
||||
}
|
||||
|
||||
Path getP2() {
|
||||
return p2;
|
||||
}
|
||||
|
||||
List<String> getStringList() {
|
||||
return stringList;
|
||||
}
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDuplicateFieldsAreRejected() {
|
||||
// field 'float' is already defined, so this should fail.
|
||||
check(EXAMPLE_BUILDER.declareString("float", (a, b) -> {
|
||||
}));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDuplicateConstructorFieldsAreRejected() {
|
||||
// field 'float' is already defined, so this should fail.
|
||||
check(EXAMPLE_BUILDER.declareString("float"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
|
@ -303,7 +188,7 @@ public class ConstructingObjectParserTest {
|
|||
|
||||
@Test
|
||||
public void testStringTransform() {
|
||||
String value = ConstructingObjectParser.transformString(input);
|
||||
String value = ObjectTransforms.transformString(input);
|
||||
assertEquals(expected, value);
|
||||
|
||||
}
|
||||
|
@ -328,7 +213,7 @@ public class ConstructingObjectParserTest {
|
|||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testFailure() {
|
||||
ConstructingObjectParser.transformString(input);
|
||||
ObjectTransforms.transformString(input);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,9 +222,9 @@ public class ConstructingObjectParserTest {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
c.declareInteger("deprecated").setDeprecated("This setting will warn the user when used.");
|
||||
c.declareInteger("obsolete", (a, b) -> {
|
||||
}).setObsolete("This setting should cause a failure when someone uses it.");
|
||||
check(c.declareInteger("deprecated").setDeprecated("This setting will warn the user when used."));
|
||||
check(c.declareInteger("obsolete", (a, b) -> {
|
||||
}).setObsolete("This setting should cause a failure when someone uses it."));
|
||||
}
|
||||
|
||||
@Test
|
|
@ -0,0 +1,112 @@
|
|||
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 p1;
|
||||
private Path p2;
|
||||
private List<String> stringList;
|
||||
|
||||
Example() {
|
||||
}
|
||||
|
||||
Example(int i, float f, long l, double d, boolean b, String s, Path p1, Path p2, List<String> stringList) {
|
||||
this.i = i;
|
||||
this.f = f;
|
||||
this.l = l;
|
||||
this.d = d;
|
||||
this.b = b;
|
||||
this.s = s;
|
||||
this.p1 = p1;
|
||||
this.p2 = p2;
|
||||
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 getP1() {
|
||||
return p1;
|
||||
}
|
||||
|
||||
void setP1(Path p1) {
|
||||
this.p1 = p1;
|
||||
}
|
||||
|
||||
Path getP2() {
|
||||
return p2;
|
||||
}
|
||||
|
||||
void setP2(Path p2) {
|
||||
this.p2 = p2;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue