Skip to content

Commit

Permalink
Translate Plugin: Simplified Config. (opensearch-project#3022)
Browse files Browse the repository at this point in the history
* Translate Plugin: Simplified Config. Added functionality for multiple sources and multiple targets

Signed-off-by: Vishal Boinapalli <vishalboinapalli3@gmail.com>

* Moved helper methods out of config file

Signed-off-by: Vishal Boinapalli <vishalboinapalli3@gmail.com>

---------

Signed-off-by: Vishal Boinapalli <vishalboinapalli3@gmail.com>
  • Loading branch information
vishalboin authored Jul 14, 2023
1 parent b7d8af1 commit ec37e23
Show file tree
Hide file tree
Showing 9 changed files with 632 additions and 363 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.opensearch.dataprepper.plugins.processor.translate;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;

import java.util.List;

public class MappingsParameterConfig {

@JsonProperty("source")
@NotNull
private Object source;

@JsonProperty("iterate_on")
private String iterateOn;

@JsonProperty("targets")
@NotNull
private List<TargetsParameterConfig> targetsParameterConfigs;


public Object getSource() {
return source;
}

public String getIterateOn() {
return iterateOn;
}

public List<TargetsParameterConfig> getTargetsParameterConfigs() {
return targetsParameterConfigs;
}

public void parseMappings(){
for(TargetsParameterConfig targetsParameterConfig: targetsParameterConfigs){
targetsParameterConfig.parseMappings();
}
}

@AssertTrue(message = "source field must be a string or list of strings")
public boolean isSourceFieldValid() {
if (source instanceof String) {
return true;
}
if (source instanceof List<?>) {
List<?> sourceList = (List<?>) source;
return sourceList.stream().allMatch(sourceItem -> sourceItem instanceof String);
}
return false;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package org.opensearch.dataprepper.plugins.processor.translate;

import org.apache.commons.lang3.Range;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.opensearch.dataprepper.model.plugin.InvalidPluginConfigurationException;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;

public class MappingsParser {
private final LinkedHashMap<Range<Float>, Object> rangeMappings = new LinkedHashMap<>();
private final Map<String, Object> individualMappings = new HashMap<>();
private final Map<Pattern, Object> compiledPatterns = new HashMap<>();
public MappingsParser(TargetsParameterConfig targetConfig){
RegexParameterConfiguration regexConfig = targetConfig.getRegexParameterConfiguration();
if (Objects.nonNull(regexConfig)) {
compilePatterns(regexConfig.getPatterns());
}
processMapField(targetConfig.getMap());
checkOverlappingKeys();
}

public Map<String, Object> fetchIndividualMappings() { return individualMappings; }

public LinkedHashMap<Range<Float>, Object> fetchRangeMappings() { return rangeMappings; }

public Map<Pattern, Object> fetchCompiledPatterns() { return compiledPatterns; }

private void compilePatterns(Map<String, Object> mappings) {
for (String pattern : mappings.keySet()) {
Pattern compiledPattern = Pattern.compile(pattern);
compiledPatterns.put(compiledPattern, mappings.get(pattern));
}
}

private void processMapField(Map<String, Object> map) {
if (Objects.nonNull(map)) {
for (Map.Entry<String, Object> mapEntry : map.entrySet()) {
parseIndividualKeys(mapEntry);
}
}
}

private void parseIndividualKeys(Map.Entry<String, Object> mapEntry) {
String[] commaSeparatedKeys = mapEntry.getKey().split(",");
for (String individualKey : commaSeparatedKeys) {
if (individualKey.contains("-")) {
addRangeMapping(Map.entry(individualKey, mapEntry.getValue()));
} else {
addIndividualMapping(individualKey, mapEntry.getValue());
}
}
}

private void addRangeMapping(Map.Entry<String, Object> mapEntry) {
String[] rangeKeys = mapEntry.getKey().split("-");
if (rangeKeys.length != 2 || !StringUtils.isNumericSpace(rangeKeys[0]) || !StringUtils.isNumericSpace(rangeKeys[1])) {
addIndividualMapping(mapEntry.getKey(), mapEntry.getValue());
} else {
Float lowKey = Float.parseFloat(rangeKeys[0]);
Float highKey = Float.parseFloat(rangeKeys[1]);
Range<Float> rangeEntry = Range.between(lowKey, highKey);
if (isRangeOverlapping(rangeEntry)) {
String exceptionMsg = "map option contains key " + mapEntry.getKey() + " that overlaps with other range entries";
throw new InvalidPluginConfigurationException(exceptionMsg);
} else {
rangeMappings.put(Range.between(lowKey, highKey), mapEntry.getValue());
}
}
}

private void addIndividualMapping(final String key, final Object value) {
if (individualMappings.containsKey(key)) {
String exceptionMsg = "map option contains duplicate entries of " + key;
throw new InvalidPluginConfigurationException(exceptionMsg);
} else {
individualMappings.put(key.strip(), value);
}
}

private boolean isRangeOverlapping(Range<Float> rangeEntry) {
return rangeMappings.keySet().stream().anyMatch(range -> range.isOverlappedBy(rangeEntry));
}

private void checkOverlappingKeys() {
for (String individualKey : individualMappings.keySet()) {
if (NumberUtils.isParsable(individualKey)) {
Float floatKey = Float.parseFloat(individualKey);
Range<Float> range = Range.between(floatKey, floatKey);
if (isRangeOverlapping(range)) {
String exceptionMsg = "map option contains key " + individualKey + " that overlaps with other range entries";
throw new InvalidPluginConfigurationException(exceptionMsg);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package org.opensearch.dataprepper.plugins.processor.translate;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.apache.commons.lang3.Range;
import org.opensearch.dataprepper.plugins.processor.mutateevent.TargetType;
import org.opensearch.dataprepper.typeconverter.TypeConverter;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class TargetsParameterConfig {
private final TypeConverter converter;
private final LinkedHashMap<Range<Float>, Object> rangeMappings = new LinkedHashMap<>();
private final Map<String, Object> individualMappings = new HashMap<>();
private final Map<Pattern, Object> compiledPatterns = new HashMap<>();
@JsonProperty("target")
@NotNull
@NotEmpty
private String target;
@JsonProperty("map")
private Map<String, Object> map;
@JsonProperty("translate_when")
private String translateWhen;
@JsonProperty("regex")
private RegexParameterConfiguration regexParameterConfig;
@JsonProperty("default")
private String defaultValue;
@JsonProperty("target_type")
private TargetType targetType = TargetType.STRING;

public TargetsParameterConfig(Map<String, Object> map, String target, RegexParameterConfiguration regexParameterConfig, String translateWhen, String defaultValue, TargetType targetType) {
this.targetType = Optional
.ofNullable(targetType)
.orElse(TargetType.STRING);
this.target = target;
this.map = map;
this.defaultValue = defaultValue;
this.regexParameterConfig = regexParameterConfig;
this.converter = this.targetType.getTargetConverter();
this.translateWhen = translateWhen;
parseMappings();
}

public String getTarget() {
return target;
}

public Map<String, Object> getMap() {
return map;
}

public String getDefaultValue() {
return defaultValue;
}

public String getTranslateWhen() {
return translateWhen;
}

public TargetType getTargetType() {
return targetType;
}

public RegexParameterConfiguration getRegexParameterConfiguration() {
return regexParameterConfig;
}

public Map<String, Object> fetchIndividualMappings() {
return individualMappings;
}

public LinkedHashMap<Range<Float>, Object> fetchRangeMappings() {
return rangeMappings;
}

public Map<Pattern, Object> fetchCompiledPatterns() {
return compiledPatterns;
}

public TypeConverter getConverter() {
return converter;
}


@AssertTrue(message = "pattern option is mandatory while configuring regex option")
public boolean isPatternPresent() {
return regexParameterConfig == null || regexParameterConfig.getPatterns() != null;
}

@AssertTrue(message = "Either map or patterns option needs to be configured under targets.")
public boolean hasMappings() {
return Stream.of(map, regexParameterConfig).filter(n -> n != null).count() != 0;
}

@AssertTrue(message = "The mapped values do not match the target type provided")
public boolean isMapTypeValid() {
return map.keySet().stream().allMatch(key -> checkTargetValueType(map.get(key)));
}

@AssertTrue(message = "The pattern values do not match the target type provided")
public boolean isPatternTypeValid() {
if (Objects.isNull(regexParameterConfig) || Objects.isNull(regexParameterConfig.getPatterns())) {
return true;
}
Map<String, Object> patterns = regexParameterConfig.getPatterns();
return patterns.keySet().stream().allMatch(key -> checkTargetValueType(patterns.get(key)));
}

private boolean checkTargetValueType(Object val) throws NumberFormatException {
if (Objects.isNull(targetType)) {
return true;
}
try {
final TypeConverter converter = targetType.getTargetConverter();
converter.convert(val);
} catch (Exception ex) {
return false;
}
return true;
}

public void parseMappings() {
MappingsParser parser = new MappingsParser(this);
individualMappings.putAll(parser.fetchIndividualMappings());
rangeMappings.putAll(parser.fetchRangeMappings());
compiledPatterns.putAll(parser.fetchCompiledPatterns());
}

}
Loading

0 comments on commit ec37e23

Please sign in to comment.