/* * 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.filter.bundle; import com.addthis.bundle.core.Bundle; import com.addthis.bundle.core.BundleFormat; import com.addthis.bundle.util.AutoField; import com.addthis.bundle.value.ValueMap; import com.addthis.bundle.value.ValueObject; import com.addthis.bundle.value.ValueTranslationException; import com.addthis.codec.annotations.FieldConfig; import com.addthis.codec.codables.Codable; import com.addthis.hydra.data.filter.value.AbstractValueFilter; import com.addthis.hydra.data.filter.value.ValueFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This {@link BundleFilter BundleFilter} <span class="hydra-summary">extracts fields from a {@link ValueMap ValueMap}</span>. * <p/> * <p>This filter will extract values from a ValueMap and store them in the current bundle. * The mapping from the ValueMap to the bundle fields are specified using an * array of {@link XMap XMap} objects. Inside an XMap object, there is the name of a key * in the ValueMap, optionally a {@link ValueFilter ValueFilter} to perform on the value * associated with the key, and optionally a bundle field name that is assigned the value. * If the bundle field name is not specified, then the name of the ValueMap key is used * as the field of the bundle. Extracted "from" fields that do not exist in the map are * ignored. This filter returns true unless the "field" argument doesn't reference a * valid ValueMap. * * <p/> * <p>See {@link XMap} for an explanation on using indirection to retrieve keys.</p> * <p/> * <p>Example:</p> * <pre> * // extract params * {map-extract {field:"QUERY_PARAMS", map:[ * {from:"key1"} * {from:"key2", to:"KEY2"} * {from:"key3", to:"KEY3"} * ]}} * </pre> * * @user-reference */ public class BundleFilterMapExtract implements BundleFilter { private static final Logger log = LoggerFactory.getLogger(BundleFilterMapExtract.class); /** * The name of the field that contains the ValueMap object. This field is required. */ @FieldConfig(codable = true, required = true) private AutoField field; /** * The mapping from the ValueMap to the bundle format. This field is required. */ @FieldConfig(codable = true, required = true) private XMap[] map; @Override public boolean filter(Bundle bundle) { ValueObject value = field.getValue(bundle); if (value == null) { return true; } ValueMap mapValue; try { mapValue = value.asMap(); } catch (ValueTranslationException vte) { log.warn("Error extracting map from value: " + value + " bundle: " + bundle); return false; } if (mapValue == null) { return true; } BundleFormat format = bundle.getFormat(); for (XMap me : map) { String key = me.from; for (int i = 0; i < me.indirection && key != null; i++) { if (format.hasField(key)) { key = bundle.getValue(format.getField(key)).asString().asNative(); } else { key = null; } } ValueObject val = mapValue.get(key); if (me.filter != null) { val = me.filter.filter(val, bundle); } if (val != null) { String toField = me.to != null ? me.to : me.from; bundle.setValue(format.getField(toField), val); } } return true; } /** * This class maps keys in the ValueMap to fields in the bundle format. * <p/> * <p>The {@link #indirection indirection} parameter can be used to interpret the * {@link #from} parameter using levels of indirection. For example if the * indirection parameter is set to '1', the 'from' parameter is set to 'abc', * and the bundle has field 'abc' with the value 'def' then the key 'def' * is used to perform the lookup into the map. When the "to" parameter is not * specified then the resulting value will always be stored in the * field specified by the "from" parameter, ie. storing the value ignores * the levels of indirection.</p> * <p/> * <p>Examples:</p> * <pre> * {from:"uid"}, // copy value of "uid" key from map into "uid" field in the bundle * {from:"uid", indirection:1}, // copy value of key specified in "uid" bundle into "uid" * field in the bundle * {from:"ln", to:"LANGUAGE"}, // copy value of "ln" key from map into "LANGUAGE" field in the bundle * {from:"uf", to:"UID_FLAGS", filter:{op:"default",value:"notset"}}, * </pre> * * @user-reference */ public static final class XMap implements Codable { /** * The name of the key in the {@link ValueMap ValueMap}. */ @FieldConfig(codable = true, required = true) private String from; /** * If non-null then assign the value to this field of the bundle. */ @FieldConfig(codable = true) private String to; /** * If non-null then apply this filter on the value retrieved from the ValueMap. Default * is null. */ @FieldConfig(codable = true) private ValueFilter filter; /** * A non-negative integer specifying the level of indirection. * Default is zero. */ @FieldConfig(codable = true) private int indirection = 0; XMap setIndirection(int indirection) { this.indirection = indirection; return this; } XMap setFrom(String from) { this.from = from; return this; } } BundleFilterMapExtract setField(AutoField field) { this.field = field; return this; } BundleFilterMapExtract setMap(XMap[] map) { this.map = map; return this; } }