/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.addthis.hydra.data.tree.prop;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.addthis.bundle.core.Bundle;
import com.addthis.bundle.util.AutoField;
import com.addthis.bundle.value.ValueArray;
import com.addthis.bundle.value.ValueFactory;
import com.addthis.bundle.value.ValueMap;
import com.addthis.bundle.value.ValueMapEntry;
import com.addthis.bundle.value.ValueObject;
import com.addthis.codec.annotations.FieldConfig;
import com.addthis.hydra.data.filter.value.ValueFilter;
import com.addthis.hydra.data.tree.DataTreeNode;
import com.addthis.hydra.data.tree.DataTreeNodeUpdater;
import com.addthis.hydra.data.tree.TreeDataParameters;
import com.addthis.hydra.data.tree.TreeNodeData;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
public final class DataCopy extends TreeNodeData<DataCopy.Config> {
/**
* This data attachment <span class="hydra-summary">stores a hashmap of key values
* captured from the current bundle</span>. You provide the list of fields to keep and
* any value filter you want to apply and this attachment will keep a copy.
* <p/>
* <p>Job Configuration Example:</p>
* <pre>
* {field:"ID", data.info.copy.key:{foo:"FOO", bar:"BAR"}}
* </pre>
*
* <p><b>Query Path Directives</b>
*
* <p>${attachment}={label[;default value[\;]]} returns the value stored under
* the given label or the optionally provided default value if the label is empty
* (eg. if none of the values passed the filter). Little unsure of
* the syntax/escaping/point of \; here.
*
* <p>% operations with no arguments return two levels of nodes. The first level
* are key nodes and the second level are value nodes.</p>
*
* @user-reference
*/
public static final class Config extends TreeDataParameters<DataCopy> {
/**
* A mapping from labels to field names. Values are taken from the field
* names and stored under the label. This field
* is required.
*/
@FieldConfig(codable = true)
private Map<String, AutoField> key;
/**
* Bundle fields that contain ValueMap objects to be stored in the data attachment.
*/
@FieldConfig(codable = true)
private AutoField[] map;
/**
* Mapping from field name to a constant label. Can be used to store sets of keys.
*/
@FieldConfig(codable = true)
private Map<String, String> set;
/**
* Mapping from field name to a a field name.
*/
@FieldConfig(codable = true)
private Map<String, AutoField> fields;
/**
* A mapping from labels to value filters. Before a value is stored under a label
* if it has a value filter then the filter is applied.
* The default is no filters.
*/
@FieldConfig(codable = true)
private Map<String, ValueFilter> op;
@Override
public DataCopy newInstance() {
DataCopy dc = new DataCopy();
dc.dat = new HashMap<>();
return dc;
}
}
@FieldConfig(codable = true, required = true)
private HashMap<String, String> dat;
@Override
public boolean updateChildData(DataTreeNodeUpdater state, DataTreeNode tn, DataCopy.Config conf) {
Bundle p = state.getBundle();
// copy values from bundle
if (conf.key != null) {
for (Entry<String, AutoField> entry : conf.key.entrySet()) {
ValueObject value = entry.getValue().getValue(p);
insertKeyValuePair(conf, p, entry.getKey(), value);
}
}
if (conf.fields != null) {
for (Entry<String, AutoField> entry : conf.fields.entrySet()) {
ValueObject value = entry.getValue().getValue(p);
String key = AutoField.newAutoField(entry.getKey()).getString(p).orElse(null);
if (key != null) {
insertKeyValuePair(conf, p, key, value);
}
}
}
if (conf.map != null) {
for (AutoField field : conf.map) {
ValueObject valueObject = field.getValue(p);
if (valueObject != null) {
ValueMap valueMap = valueObject.asMap();
for (ValueMapEntry entry : valueMap) {
insertKeyValuePair(conf, p, entry.getKey(), entry.getValue());
}
}
}
}
if (conf.set != null) {
for (Entry<String, String> entry : conf.set.entrySet()) {
ValueObject keyObject = AutoField.newAutoField(entry.getKey()).getValue(p);
if (keyObject != null) {
ValueArray keyArray = keyObject.asArray();
for (ValueObject object : keyArray) {
insertKeyValuePair(object.toString(), entry.getValue());
}
}
}
}
return true;
}
private void insertKeyValuePair(Config conf, Bundle p, String key, ValueObject value) {
if (conf.op != null) {
ValueFilter fo = conf.op.get(key);
if ((fo != null) && (value != null)) {
value = fo.filter(value, p);
}
}
if (value != null) {
insertKeyValuePair(key, value.toString());
}
}
private void insertKeyValuePair(String key, String value) {
if (value != null) {
dat.put(key, value);
}
}
@Override
public Collection<String> getValueTypes() {
return dat.keySet();
}
@Override
public ValueObject getValue(String key) {
String dv = null;
if (dat != null) {
int ci = key.indexOf(';');
int eci = key.indexOf("\\;");
if ((ci > 0) && ((eci - ci) != 1)) {
dv = key.substring(ci + 1);
key = key.substring(0, ci);
}
String v = dat.get(key);
if ((v != null) && !v.isEmpty()) {
return ValueFactory.create(v);
}
}
if (dv != null) {
return ValueFactory.create(dv);
} else {
return null;
}
}
/**
* Convenience method to convert an node into an array of size one.
*/
private static VirtualTreeNode[] generateSingletonArray(VirtualTreeNode value) {
VirtualTreeNode[] result = new VirtualTreeNode[1];
result[0] = value;
return result;
}
@Override
public List<DataTreeNode> getNodes(DataTreeNode node, String key) {
if (dat == null) {
return ImmutableList.of();
}
if (Strings.isNullOrEmpty(key)) {
List<DataTreeNode> result = new ArrayList<>();
for (Map.Entry<String, String> entry : dat.entrySet()) {
String dataKey = entry.getKey();
String dataValue = entry.getValue();
VirtualTreeNode child = new VirtualTreeNode(dataValue, 1);
VirtualTreeNode parent = new VirtualTreeNode(dataKey, 1, generateSingletonArray(child));
result.add(parent);
}
return result;
} else {
return ImmutableList.of();
}
}
}