/*
* JBoss, Home of Professional Open Source
* Copyright 2012 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
*/
package org.jboss.elasticsearch.tools.content;
import java.util.Collection;
import java.util.Map;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
/**
* Content preprocessor which allows to perform mapping of simple value from source field over configured Map mapping
* structure to another or same target field. Optional default value can be used for values not found in mapping.
* Example of configuration for this preprocessor:
*
* <pre>
* {
* "name" : "Status Normalizer",
* "class" : "org.jboss.elasticsearch.tools.content.SimpleValueMapMapperPreprocessor",
* "settings" : {
* "source_field" : "fields.status.name",
* "target_field" : "dcp_issue_status",
* "value_default" : "In Progress",
* "value_mapping" : {
* "Open" : "Open",
* "Resolved" : "Closed",
* "Closed" : "Closed"
* }
* }
* }
* </pre>
*
* Options are:
* <ul>
* <li><code>source_field</code> - source field in input data. Dot notation for nested values can be used here (see
* {@link XContentMapValues#extractValue(String, Map)}).
* <li><code>target_field</code> - target field in data to store mapped value into. Can be same as input field. Dot
* notation can be used here for structure nesting.
* <li><code>value_default</code> - optional default value used if <code>value_mapping</code> Map do not provide
* mapping. If not set then target field is leaved empty for values not found in mapping. You can use pattern for keys
* replacement from other values in data in default value. Keys are enclosed in curly braces, dot notation for deeper
* nesting may be used in keys. Special key '<code>__original</code>' means that original value from source field will
* be used here. Example of default value with replacement keys ' <code>No mapping found for value {__original}.</code>
* '.
* <li><code>value_mapping</code> - Map structure for value mapping. Key is value from <code>source_field</code>, Value
* is value for for <code>target_field</code>.
* </ul>
*
* @author Vlastimil Elias (velias at redhat dot com)
* @see StructuredContentPreprocessorFactory
* @see ValueUtils#processStringValuePatternReplacement(String, Map, Object)
*/
public class SimpleValueMapMapperPreprocessor extends StructuredContentPreprocessorBase {
protected static final String CFG_SOURCE_FIELD = "source_field";
protected static final String CFG_TARGET_FIELD = "target_field";
protected static final String CFG_VALUE_DEFAULT = "value_default";
protected static final String CFG_VALUE_MAPPING = "value_mapping";
protected String fieldSource;
protected String fieldTarget;
protected String defaultValue = null;
protected Map<String, String> valueMap = null;
@SuppressWarnings("unchecked")
@Override
public void init(Map<String, Object> settings) throws SettingsException {
if (settings == null) {
throw new SettingsException("'settings' section is not defined for preprocessor " + name);
}
fieldSource = XContentMapValues.nodeStringValue(settings.get(CFG_SOURCE_FIELD), null);
validateConfigurationStringNotEmpty(fieldSource, CFG_SOURCE_FIELD);
fieldTarget = XContentMapValues.nodeStringValue(settings.get(CFG_TARGET_FIELD), null);
validateConfigurationStringNotEmpty(fieldTarget, CFG_TARGET_FIELD);
defaultValue = ValueUtils.trimToNull(XContentMapValues.nodeStringValue(settings.get(CFG_VALUE_DEFAULT), null));
valueMap = (Map<String, String>) settings.get(CFG_VALUE_MAPPING);
if (valueMap == null || valueMap.isEmpty()) {
logger.warn("'settings/" + CFG_VALUE_MAPPING + "' is not defined for preprocessor '{}'", name);
}
}
@Override
public Map<String, Object> preprocessData(Map<String, Object> data, PreprocessChainContext chainContext) {
if (data == null)
return null;
Object v = null;
if (fieldSource.contains(".")) {
v = XContentMapValues.extractValue(fieldSource, data);
} else {
v = data.get(fieldSource);
}
if (v == null) {
putDefaultValue(data, null);
} else if (v instanceof Map || v instanceof Collection || v.getClass().isArray()) {
String msg = "Value for field '" + fieldSource
+ "' is not simple value (but is List or Array or Map), so can't be processed";
addDataWarning(chainContext, msg);
logger.debug(msg);
} else {
String origValue = v.toString();
String newVal = null;
if (valueMap != null && !ValueUtils.isEmpty(origValue))
newVal = valueMap.get(origValue);
if (newVal != null) {
putTargetValue(data, newVal);
} else {
putDefaultValue(data, origValue);
}
}
return data;
}
private void putDefaultValue(Map<String, Object> data, String originalValue) {
if (defaultValue != null) {
putTargetValue(data, ValueUtils.processStringValuePatternReplacement(defaultValue, data, originalValue));
}
}
protected void putTargetValue(Map<String, Object> data, String value) {
StructureUtils.putValueIntoMapOfMaps(data, fieldTarget, value);
}
public String getFieldSource() {
return fieldSource;
}
public String getFieldTarget() {
return fieldTarget;
}
public String getDefaultValue() {
return defaultValue;
}
public Map<String, String> getValueMap() {
return valueMap;
}
}