Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions logstash-core/src/test/java/org/logstash/plugin/Conversion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.logstash.plugin;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

enum Conversion {
String,
Integer,
Float,
Boolean;

static Optional<Conversion> lookup(String value) {
return Arrays.stream(Conversion.values()).filter(c -> c.toString().toLowerCase().equals(value.toLowerCase())).findFirst();
}

static List<String> names() {
return Arrays.stream(Conversion.values()).map(Object::toString).map(java.lang.String::toLowerCase).collect(Collectors.toList());
}

Object convert(Object value) {
switch (this) {
case String:
return value.toString();
case Integer:
return java.lang.Integer.parseInt((String) value);
case Float:
return java.lang.Float.parseFloat((String) value);
case Boolean:
switch (((String) value).toLowerCase()) {
case "true":
return true;
case "false":
return false;
default:
throw new IllegalArgumentException("Boolean convert only works on values 'true' or 'false' (case insensitive)");
}
}

assert (false); // should not get here
throw new IllegalArgumentException("BUG: Unreachable code has been reached.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.logstash.plugin;

import org.logstash.Event;
import org.logstash.FieldReference;
import org.logstash.Javafier;
import org.logstash.PathCache;

import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;

/**
* A builder for the Mutate processor which builds a chain of Consumers.
* <p>
* This differs from the Ruby implementation by using build-time knowledge to build a consumer that
* executes exactly the required steps and nothing more.
* <p>
* In Ruby, we always check if @convert is set, for example, and this model (chaining Consumer.andThen),
* we can build a Consumer that doesn't waste any energy on things never configured by the user.
* <p>
* XXX: Would this be similarly efficient as a Processor that lazily built itself?
* <p>
* Something like:
* void process(Event event) {
* if (this.consumer == null) { build(); }
* this.consumer.accept(event);
* }
*/
class MutateProcessorBuilder {
private Consumer<Event> consumer;

MutateProcessorBuilder withConvert(Map<String, Object> map) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
FieldReference ref = PathCache.cache(entry.getKey());
Optional<Conversion> result = Conversion.lookup((String) entry.getValue());
if (!result.isPresent()) {
throw new IllegalArgumentException("Invalid conversion '" + entry.getValue() + "'. Must be one of: " + Conversion.names());
}

Conversion conversion = result.get();

append((Event event) -> {
if (event.includes(ref)) {
Object value = event.getUnconvertedField(ref);
if (value != null) {
event.setField(ref, conversion.convert(Javafier.deep(value)));
}
}
});
}

return this;
}

private void append(Consumer<Event> consumer) {
if (this.consumer == null) {
this.consumer = consumer;
} else {
//System.out.println("Appending consumer: " + consumer);
this.consumer = this.consumer.andThen(consumer);
}
}


Consumer<Event> build() {
return consumer;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.logstash.plugin;

import org.junit.Test;
import org.logstash.Event;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class MutateProcessorTest {
private MutateProcessorBuilder builder = new MutateProcessorBuilder();

private Map<String, Object> conversions = new HashMap<String, Object>() {{
put("foo", "integer");
put("bar", "float");
put("baz", "boolean");
}};

@Test
public void testBuilder() {
builder.withConvert(conversions);
Consumer<Event> consumer = builder.build();

Event event = new Event();
event.setField("foo", "1");
event.setField("bar", "1");
event.setField("baz", "true");

consumer.accept(event);

// Ok so... Event actually stores Long, not Integer.
assertThat("foo type is integer", event.getField("foo"), instanceOf(Long.class));
assertThat("foo is 1", event.getField("foo"), is(1L));

assertThat("bar type is float", event.getField("bar"), instanceOf(Double.class));
assertThat("bar is 1.0", event.getField("bar"), is(1.0));

assertThat("baz type is boolean", event.getField("baz"), instanceOf(Boolean.class));
assertThat("baz is true", event.getField("baz"), is(true));
}
}