diff --git a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/event/DefaultEventMetadata.java b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/event/DefaultEventMetadata.java index bf1857a9b3..24384c2ad3 100644 --- a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/event/DefaultEventMetadata.java +++ b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/event/DefaultEventMetadata.java @@ -5,12 +5,11 @@ package org.opensearch.dataprepper.model.event; -import com.google.common.collect.ImmutableMap; - import java.time.Instant; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.HashMap; import java.util.Objects; import java.util.Set; @@ -28,7 +27,7 @@ public class DefaultEventMetadata implements EventMetadata { private final Instant timeReceived; - private final ImmutableMap attributes; + private Map attributes; private Set tags; @@ -41,7 +40,7 @@ private DefaultEventMetadata(final Builder builder) { this.timeReceived = builder.timeReceived == null ? Instant.now() : builder.timeReceived; - this.attributes = builder.attributes == null ? ImmutableMap.of() : ImmutableMap.copyOf(builder.attributes); + this.attributes = builder.attributes == null ? new HashMap<>() : new HashMap<>(builder.attributes); this.tags = builder.tags == null ? new HashSet<>() : new HashSet(builder.tags); } @@ -49,7 +48,7 @@ private DefaultEventMetadata(final Builder builder) { private DefaultEventMetadata(final EventMetadata eventMetadata) { this.eventType = eventMetadata.getEventType(); this.timeReceived = eventMetadata.getTimeReceived(); - this.attributes = ImmutableMap.copyOf(eventMetadata.getAttributes()); + this.attributes = new HashMap<>(eventMetadata.getAttributes()); this.tags = new HashSet<>(eventMetadata.getTags()); } @@ -68,6 +67,11 @@ public Map getAttributes() { return attributes; } + @Override + public void setAttribute(final String key, final Object value) { + attributes.put(key, value); + } + @Override public Object getAttribute(final String attributeKey) { String key = (attributeKey.charAt(0) == '/') ? attributeKey.substring(1) : attributeKey; diff --git a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/event/EventMetadata.java b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/event/EventMetadata.java index 180d38ec12..511a87c1fa 100644 --- a/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/event/EventMetadata.java +++ b/data-prepper-api/src/main/java/org/opensearch/dataprepper/model/event/EventMetadata.java @@ -46,6 +46,14 @@ public interface EventMetadata extends Serializable { */ Object getAttribute(final String key); + /** + * Sets an attribute + * @param key to be set + * @param value to be set + * @since 2.3 + */ + void setAttribute(String key, Object value); + /** * Returns the tags * @return a set of tags diff --git a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/event/DefaultEventMetadataTest.java b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/event/DefaultEventMetadataTest.java index fe4fc18d38..18f15f60d9 100644 --- a/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/event/DefaultEventMetadataTest.java +++ b/data-prepper-api/src/test/java/org/opensearch/dataprepper/model/event/DefaultEventMetadataTest.java @@ -111,22 +111,16 @@ public void testGetAttribute(final String key, final Object value) { assertThat(eventMetadata.getAttribute(key+"/key6"), equalTo(null)); } - @Test - public void testAttributesMutation_throwsAnException() { - final Map attributes = eventMetadata.getAttributes(); - - assertThrows(UnsupportedOperationException.class, () -> attributes.put("foo", "bar")); - } - - @Test - public void testAttributesMutation_without_attributes_throwsAnException() { + @ParameterizedTest + @MethodSource("getAttributeTestInputs") + public void testSetAttribute(String key, final Object value) { eventMetadata = DefaultEventMetadata.builder() .withEventType(testEventType) .withTimeReceived(testTimeReceived) .build(); - final Map attributes = eventMetadata.getAttributes(); - - assertThrows(UnsupportedOperationException.class, () -> attributes.put("foo", "bar")); + key = (key.charAt(0) == '/') ? key.substring(1) : key; + eventMetadata.setAttribute(key, value); + assertThat(eventMetadata.getAttribute(key), equalTo(value)); } @Test @@ -139,7 +133,6 @@ public void testAttributes_without_attributes_is_empty() { assertThat(attributes, notNullValue()); assertThat(attributes.size(), equalTo(0)); - assertThrows(UnsupportedOperationException.class, () -> attributes.put("foo", "bar")); } @Test diff --git a/data-prepper-core/src/main/java/org/opensearch/dataprepper/event/DefaultBaseEventBuilder.java b/data-prepper-core/src/main/java/org/opensearch/dataprepper/event/DefaultBaseEventBuilder.java index 734f14d867..d0aa67bbab 100644 --- a/data-prepper-core/src/main/java/org/opensearch/dataprepper/event/DefaultBaseEventBuilder.java +++ b/data-prepper-core/src/main/java/org/opensearch/dataprepper/event/DefaultBaseEventBuilder.java @@ -11,8 +11,8 @@ import org.opensearch.dataprepper.model.event.DefaultEventMetadata; import java.util.Map; +import java.util.HashMap; import java.time.Instant; -import com.google.common.collect.ImmutableMap; abstract class DefaultBaseEventBuilder implements BaseEventBuilder{ private EventMetadata eventMetadata; @@ -70,7 +70,7 @@ public BaseEventBuilder withTimeReceived(final Instant timeReceived) { public BaseEventBuilder withEventMetadata(final EventMetadata eventMetadata) { this.eventType = eventMetadata.getEventType(); this.timeReceived = eventMetadata.getTimeReceived(); - this.attributes = ImmutableMap.copyOf(eventMetadata.getAttributes()); + this.attributes = new HashMap<>(eventMetadata.getAttributes()); return this; } diff --git a/data-prepper-plugins/mutate-event-processors/README.md b/data-prepper-plugins/mutate-event-processors/README.md index 737f33f29f..63bbfdab4d 100644 --- a/data-prepper-plugins/mutate-event-processors/README.md +++ b/data-prepper-plugins/mutate-event-processors/README.md @@ -56,7 +56,8 @@ then when we run with the same input, the processor will parse the message into ### Configuration * `entries` - (required) - A list of entries to add to an event - * `key` - (required) - The key of the new entry to be added + * `key` - (required) - The key of the new entry to be added. One of `key` or `metadata_key` is required. + * `metadata_key` - (required) - The key of the new metadata attribute to be added. Argument must be a literal string key and not JsonPointer. One of `key` or `metadata_key` is required. * `value` - (optional) - The value of the new entry to be added. Strings, booleans, numbers, null, nested objects, and arrays containing the aforementioned data types are valid to use. Required if `format` is not specified. * `format` - (optional) - A format string to use as value of the new entry to be added. For example, `${key1}-${ke2}` where `key1` and `key2` are existing keys in the event. Required if `value` is not specified. * `overwrite_if_key_exists` - (optional) - When set to `true`, if `key` already exists in the event, then the existing value will be overwritten. The default is `false`. diff --git a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java index e749d979b3..33877a8f36 100644 --- a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java +++ b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java @@ -18,6 +18,7 @@ import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; import static org.opensearch.dataprepper.logging.DataPrepperMarkers.EVENT; @@ -48,16 +49,28 @@ public Collection> doExecute(final Collection> recor } try { - if (!recordEvent.containsKey(entry.getKey()) || entry.getOverwriteIfKeyExists()) { - if (!Objects.isNull(entry.getFormat())) { - recordEvent.put(entry.getKey(), recordEvent.formatString(entry.getFormat())); - } else { - recordEvent.put(entry.getKey(), entry.getValue()); + final String key = entry.getKey(); + final String metadataKey = entry.getMetadataKey(); + Object value; + if (!Objects.isNull(entry.getFormat())) { + value = recordEvent.formatString(entry.getFormat()); + } else { + value = entry.getValue(); + } + if (!Objects.isNull(key)) { + if (!recordEvent.containsKey(key) || entry.getOverwriteIfKeyExists()) { + recordEvent.put(key, value); + } + } else { + Map attributes = recordEvent.getMetadata().getAttributes(); + if (!attributes.containsKey(metadataKey) || entry.getOverwriteIfKeyExists()) { + recordEvent.getMetadata().setAttribute(metadataKey, value); + } } } catch (Exception e) { - LOG.error(EVENT, "Error adding entry to record [{}] with key [{}], format [{}], value [{}]", - entry.getKey(), entry.getValue(), entry.getFormat(), recordEvent, e); + LOG.error(EVENT, "Error adding entry to record [{}] with key [{}], metadataKey [{}], format [{}], value [{}]", + recordEvent, entry.getKey(), entry.getMetadataKey(), entry.getFormat(), entry.getValue(), e); } } } diff --git a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java index 61bd4a7bc0..bc62de1681 100644 --- a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java +++ b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java @@ -16,10 +16,10 @@ public class AddEntryProcessorConfig { public static class Entry { - @NotEmpty - @NotNull private String key; + private String metadataKey; + private Object value; private String format; @@ -34,6 +34,10 @@ public String getKey() { return key; } + public String getMetadataKey() { + return metadataKey; + } + public Object getValue() { return value; } @@ -54,12 +58,20 @@ public boolean hasValueOrFormat() { } public Entry(final String key, + final String metadataKey, final Object value, final String format, final boolean overwriteIfKeyExists, final String addWhen) { + if (key != null && metadataKey != null) { + throw new IllegalArgumentException("Only one of the two - key and metadatakey - should be specified"); + } + if (key == null && metadataKey == null) { + throw new IllegalArgumentException("At least one of the two - key and metadatakey - must be specified"); + } this.key = key; + this.metadataKey = metadataKey; this.value = value; this.format = format; this.overwriteIfKeyExists = overwriteIfKeyExists; diff --git a/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java b/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java index 280aec5bf5..d520f5cb6d 100644 --- a/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java +++ b/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java @@ -26,6 +26,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -45,7 +46,7 @@ public class AddEntryProcessorTests { @Test public void testSingleAddProcessorTests() { - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", 3, null, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, 3, null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -59,8 +60,8 @@ public void testSingleAddProcessorTests() { @Test public void testMultiAddProcessorTests() { - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", 3, null, false, null), - createEntry("message2", 4, null, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, 3, null, false, null), + createEntry("message2", null, 4, null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -76,7 +77,7 @@ public void testMultiAddProcessorTests() { @Test public void testSingleNoOverwriteAddProcessorTests() { - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", 3, null, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, 3, null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -91,7 +92,7 @@ public void testSingleNoOverwriteAddProcessorTests() { @Test public void testSingleOverwriteAddProcessorTests() { - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", 3, null, true, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, 3, null, true, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -106,8 +107,8 @@ public void testSingleOverwriteAddProcessorTests() { @Test public void testMultiOverwriteMixedAddProcessorTests() { - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", 3, null, true, null), - createEntry("message", 4, null, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, 3, null, true, null), + createEntry("message", null, 4, null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -122,7 +123,7 @@ public void testMultiOverwriteMixedAddProcessorTests() { @Test public void testIntAddProcessorTests() { - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", 3, null, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, 3, null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -136,7 +137,7 @@ public void testIntAddProcessorTests() { @Test public void testBoolAddProcessorTests() { - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", true, null, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, true, null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -150,7 +151,7 @@ public void testBoolAddProcessorTests() { @Test public void testStringAddProcessorTests() { - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", "string", null, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, "string", null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -164,7 +165,7 @@ public void testStringAddProcessorTests() { @Test public void testNullAddProcessorTests() { - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, null, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, null, null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -190,7 +191,7 @@ public boolean equals(Object o) { public void testNestedAddProcessorTests() { TestObject obj = new TestObject(); obj.a = "test"; - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", obj, null, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, obj, null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -205,7 +206,7 @@ public void testNestedAddProcessorTests() { @Test public void testArrayAddProcessorTests() { Object[] array = new Object[] { 1, 1.2, "string", true, null }; - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", array, null, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, array, null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -220,7 +221,7 @@ public void testArrayAddProcessorTests() { @Test public void testFloatAddProcessorTests() { when(mockConfig.getEntries()) - .thenReturn(createListOfEntries(createEntry("newMessage", 1.2, null, false, null))); + .thenReturn(createListOfEntries(createEntry("newMessage", null, 1.2, null, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -235,7 +236,7 @@ public void testFloatAddProcessorTests() { @Test public void testAddSingleFormatEntry() { when(mockConfig.getEntries()) - .thenReturn(createListOfEntries(createEntry("date-time", null, TEST_FORMAT, false, null))); + .thenReturn(createListOfEntries(createEntry("date-time", null, null, TEST_FORMAT, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getTestEventWithMultipleFields(); @@ -250,8 +251,8 @@ public void testAddSingleFormatEntry() { @Test public void testAddMultipleFormatEntries() { when(mockConfig.getEntries()) - .thenReturn(createListOfEntries(createEntry("date-time", null, TEST_FORMAT, false, null), - createEntry("date-time2", null, ANOTHER_TEST_FORMAT, false, null))); + .thenReturn(createListOfEntries(createEntry("date-time", null, null, TEST_FORMAT, false, null), + createEntry("date-time2", null, null, ANOTHER_TEST_FORMAT, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getTestEventWithMultipleFields(); @@ -268,7 +269,7 @@ public void testAddMultipleFormatEntries() { public void testFormatOverwritesExistingEntry() { when(mockConfig.getEntries()) .thenReturn( - createListOfEntries(createEntry("time", null, TEST_FORMAT, true, null))); + createListOfEntries(createEntry("time", null, null, TEST_FORMAT, true, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getTestEventWithMultipleFields(); @@ -283,7 +284,7 @@ public void testFormatOverwritesExistingEntry() { public void testFormatNotOverwriteExistingEntry() { when(mockConfig.getEntries()) .thenReturn( - createListOfEntries(createEntry("time", null, TEST_FORMAT, false, null))); + createListOfEntries(createEntry("time", null, null, TEST_FORMAT, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getTestEventWithMultipleFields(); @@ -299,7 +300,7 @@ public void testFormatNotOverwriteExistingEntry() { public void testFormatPrecedesValue() { when(mockConfig.getEntries()) .thenReturn( - createListOfEntries(createEntry("date-time", "date-time-value", TEST_FORMAT, false, null))); + createListOfEntries(createEntry("date-time", null, "date-time-value", TEST_FORMAT, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getTestEventWithMultipleFields(); @@ -314,7 +315,7 @@ public void testFormatPrecedesValue() { @Test public void testFormatVariousDataTypes() { when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry( - "newField", null, "${number-key}-${boolean-key}-${string-key}", false, null))); + "newField", null, null, "${number-key}-${boolean-key}-${string-key}", false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getTestEventWithMultipleDataTypes(); @@ -326,7 +327,7 @@ public void testFormatVariousDataTypes() { @Test public void testBadFormatThenEntryNotAdded() { - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("data-time", null, BAD_TEST_FORMAT, false, null))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("data-time", null, null, BAD_TEST_FORMAT, false, null))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getTestEventWithMultipleFields(); @@ -338,11 +339,25 @@ public void testBadFormatThenEntryNotAdded() { assertThat(event.containsKey("data-time"), equalTo(false)); } + @Test + public void testMetadataKeySetWithBadFormatThenEntryNotAdded() { + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry(null,"data-time", null, BAD_TEST_FORMAT, false, null))); + + final AddEntryProcessor processor = createObjectUnderTest(); + final Record record = getEventWithMetadata("message", Map.of("date", "date-value", "time", "time-value")); + final List> editedRecords = (List>) processor.doExecute(Collections.singletonList(record)); + + Event event = editedRecords.get(0).getData(); + Map attributes = event.getMetadata().getAttributes(); + assertThat(event.get("date", Object.class), equalTo("date-value")); + assertThat(event.get("time", Object.class), equalTo("time-value")); + assertThat(attributes.containsKey("data-time"), equalTo(false)); + } @Test public void testKeyIsNotAdded_when_addWhen_condition_is_false() { final String addWhen = UUID.randomUUID().toString(); - when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", 3, null, false, addWhen))); + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry("newMessage", null, 3, null, false, addWhen))); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("thisisamessage"); @@ -355,13 +370,92 @@ public void testKeyIsNotAdded_when_addWhen_condition_is_false() { assertThat(editedRecords.get(0).getData().containsKey("newMessage"), is(false)); } + @Test + public void testMetadataKeyIsNotAdded_when_addWhen_condition_is_false() { + final String addWhen = UUID.randomUUID().toString(); + + when(mockConfig.getEntries()).thenReturn(createListOfEntries(createEntry(null, "newMessage", 3, null, false, addWhen))); + + final AddEntryProcessor processor = createObjectUnderTest(); + final Record record = getEventWithMetadata("thisisamessage", Map.of("key", "value")); + + when(expressionEvaluator.evaluate(addWhen, record.getData())).thenReturn(false); + final List> editedRecords = (List>) processor.doExecute(Collections.singletonList(record)); + + Event event = editedRecords.get(0).getData(); + assertThat(event.containsKey("message"), is(true)); + assertThat(event.get("message", Object.class), equalTo("thisisamessage")); + Map attributes = event.getMetadata().getAttributes(); + assertThat(attributes.containsKey("newMessage"), is(false)); + } + + @Test + public void testMetadataKeySetWithDifferentDataTypes() { + when(mockConfig.getEntries()).thenReturn(createListOfEntries( + createEntry(null, "newField", "newValue", null, false, null), + createEntry(null, "newIntField", 123, null, false, null), + createEntry(null, "newBooleanField", true, null, false, null) + )); + + final AddEntryProcessor processor = createObjectUnderTest(); + final Record record = getEventWithMetadata("message", Map.of("key1", "value1")); + final List> editedRecords = (List>) processor.doExecute(Collections.singletonList(record)); + + Map attributes = editedRecords.get(0).getData().getMetadata().getAttributes(); + assertThat(attributes.get("newField"), equalTo("newValue")); + assertThat(attributes.get("newIntField"), equalTo(123)); + assertThat(attributes.get("newBooleanField"), equalTo(true)); + } + + @Test + public void testMetadataKeySetWithFormatNotOverwriteExistingEntry() { + when(mockConfig.getEntries()) + .thenReturn( + createListOfEntries(createEntry(null, "time", null, TEST_FORMAT, false, null))); + + final AddEntryProcessor processor = createObjectUnderTest(); + final Record record = getEventWithMetadata("message", Map.of("date", "date-value", "time", "time-value")); + final List> editedRecords = (List>) processor.doExecute(Collections.singletonList(record)); + + Map attributes = editedRecords.get(0).getData().getMetadata().getAttributes(); + assertThat(attributes.get("date"), equalTo("date-value")); + assertThat(attributes.get("time"), equalTo("time-value")); + assertThat(attributes.containsKey("date-time"), equalTo(false)); + } + + @Test + public void testMetadataKeySetWithFormatOverwriteExistingEntry() { + when(mockConfig.getEntries()) + .thenReturn( + createListOfEntries(createEntry(null, "time", null, TEST_FORMAT, true, null))); + + final AddEntryProcessor processor = createObjectUnderTest(); + final Record record = getEventWithMetadata("message", Map.of("date", "date-value", "time", "time-value")); + final List> editedRecords = (List>) processor.doExecute(Collections.singletonList(record)); + + Map attributes = editedRecords.get(0).getData().getMetadata().getAttributes(); + assertThat(attributes.get("date"), equalTo("date-value")); + assertThat(attributes.get("time"), equalTo("date-value time-value")); + assertThat(attributes.containsKey("date-time"), equalTo(false)); + } + + @Test + public void testMetadataKeyAndKeyBothNotSetThrows() { + assertThrows(IllegalArgumentException.class, () -> createEntry(null, null, "newValue", null, false, null)); + } + + @Test + public void testMetadataKeyAndKeyBothSetThrows() { + assertThrows(IllegalArgumentException.class, () -> createEntry("newKey", "newMetadataKey", "newValue", null, false, null)); + } + private AddEntryProcessor createObjectUnderTest() { return new AddEntryProcessor(pluginMetrics, mockConfig, expressionEvaluator); } private AddEntryProcessorConfig.Entry createEntry( - final String key, final Object value, final String format, final boolean overwriteIfKeyExists, final String addWhen) { - return new AddEntryProcessorConfig.Entry(key, value, format, overwriteIfKeyExists, addWhen); + final String key, final String metadataKey, final Object value, final String format, final boolean overwriteIfKeyExists, final String addWhen) { + return new AddEntryProcessorConfig.Entry(key, metadataKey, value, format, overwriteIfKeyExists, addWhen); } private List createListOfEntries(final AddEntryProcessorConfig.Entry... entries) { @@ -393,4 +487,17 @@ private static Record buildRecordWithEvent(final Map data .withEventType("event") .build()); } + + private Record getEventWithMetadata(String message, Map attributes) { + final Map testData = new HashMap<>(); + testData.put("message", message); + testData.put("date", "date-value"); + testData.put("time", "time-value"); + return new Record<>(JacksonEvent.builder() + .withData(testData) + .withEventMetadataAttributes(attributes) + .withEventType("event") + .build()); + } + }