/* * 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.task.output.tree; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import com.addthis.bundle.util.AutoField; import com.addthis.codec.annotations.FieldConfig; import com.addthis.codec.annotations.Pluggable; import com.addthis.codec.codables.Codable; import com.addthis.hydra.data.filter.bundle.BundleFilter; import com.addthis.hydra.data.tree.DataTreeNode; import com.addthis.hydra.data.tree.TreeDataParameters; import com.addthis.hydra.data.tree.TreeDataParent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /* * TODO clean up preCheck in element, event* * TODO add alias nodes/map that use 'hard' links across the tree for cross-indexes (doesn't PathAlias do this?) * TODO extend feature support (on/off) to data elements on nodes */ /** * A path element defines a component of the tree structure. * Depending on the path element it will define one or more nodes of the tree.</p> * <p/> * @user-reference * @hydra-category Path Elements * @hydra-doc-position 7 * @exclude-fields name, count, op, term, feature, featureOff, data, filter, profileCalls, profileTime, disabled */ @Pluggable("path element") public abstract class PathElement implements Codable, TreeDataParent { protected static final Logger log = LoggerFactory.getLogger(PathElement.class); protected static final boolean debug = System.getProperty("hydra.path.debug", "0").equals("1"); protected static final boolean defCountHit = System.getProperty("hydra.default.counthit", "1").equals("1"); public static final HashSet<String> featureSet = new HashSet<>(); /** * If non-null then a parent node is inserted * into the path. The parent node is a "{@link PathValue const}" node * with a value that is equal to the value of this parameter. */ @FieldConfig(codable = true) protected String name; /** * If true then record the number of bundles * that are processed by this path element. * Default is "hydra.default.counthit" configuration value * (as either "1" or "0") or true. */ @FieldConfig(codable = true) protected boolean count = defCountHit; /** * If non-null then use this bundle field to assign * a value to the hits property. Default is null. */ @FieldConfig(codable = true) protected AutoField hitsField; /** * If true then continue processing child path elements * even when the current path element does not have * any more data to process. Default is false. */ @FieldConfig(codable = true) protected boolean op; /** * If true then do not process any subsequent path * elements in the enclosing sequence of path elements. * Default is false. */ @FieldConfig(codable = true) protected boolean term; /** * The set of features for this path element. * If this set contains one or more elements that * are not included in the {@link TreeMapper#features features} * of the tree then disable this path element. */ @FieldConfig(codable = true) protected HashSet<String> feature; /** * The set of features to disable this path element. * If this set contains one or more elements that * are included in the {@link TreeMapper#features features} * of the tree then disable this path element. */ @FieldConfig(codable = true) protected HashSet<String> featureOff; /** * Definition of the data attachments. * Consists of a mapping from the name of a data attachment * to the structure of the data attachment. */ @SuppressWarnings("unchecked") @FieldConfig(codable = true) protected HashMap<String, TreeDataParameters> data; /** * Optional {@linkplain BundleFilter bundle filter} to apply on incoming bundles. * Only bundles that pass the filter will be processed. * Default is null. */ @FieldConfig(codable = true) protected BundleFilter filter; private PathValue label; @FieldConfig(codable = true, writeonly = true) private AtomicLong profileCalls; @FieldConfig(codable = true, writeonly = true) private AtomicLong profileTime; @FieldConfig(codable = true, writeonly = true) private boolean disabled; public PathElement() { } public String toString() { return this.getClass().getSimpleName(); } public static String intern(String s) { return s != null ? s.intern() : null; } private final void initProfile() { if (profileCalls == null || profileTime == null) { clearProfile(); } } public void clearProfile() { profileCalls = new AtomicLong(0); profileTime = new AtomicLong(0); } public long getProfileCalls() { initProfile(); return profileCalls.get(); } public long getProfileTime() { initProfile(); return profileTime.get(); } public void updateProfile(long time) { initProfile(); profileCalls.incrementAndGet(); profileTime.addAndGet(time); } /** * resolve one-bind bindings (done post-parsing from Config) */ public void resolve(final TreeMapper mapper) { if (feature != null) { for (String f : feature) { if (!featureSet.contains(f)) { disabled = true; log.warn("feature disabled " + this + ", missing '" + f + "'"); break; } } } if (featureOff != null) { for (String f : featureOff) { if (featureSet.contains(f)) { disabled = true; log.warn("feature disabled " + this + ", enabled '" + f + "'"); break; } } } if (name != null) { label = new PathValue(intern(name)); label.count = count; } } public final boolean isOp() { return op; } /** * wrapper that calls getPathValue to prevent multiple calls */ public final List<DataTreeNode> processNode(final TreeMapState state) { if (debug) { log.warn("processNode<{}>", this); } List<DataTreeNode> list = null; if (filter == null || filter.filter(state.getBundle())) { if (label != null) { state.push(label.processNode(state)); list = getNextNodeList(state); state.pop().release(); } else { list = getNextNodeList(state); } } if (term) { if (list != null) { list.forEach(DataTreeNode::release); } return null; } else { if (op) { if (list != null) { list.forEach(DataTreeNode::release); } return TreeMapState.empty(); } else { return list; } } } /** * override in subclasses * * @return list of child nodes of current node to process next */ public abstract List<DataTreeNode> getNextNodeList(final TreeMapState state); public final PathElement label() { return label; } @Override public final boolean countHits() { return count; } @Override public final boolean assignHits() { return hitsField != null; } @SuppressWarnings("unchecked") @Override public HashMap<String, TreeDataParameters> dataConfig() { return data; } @SuppressWarnings("unchecked") public PathElement setData(final HashMap<String, TreeDataParameters> data) { this.data = data; return this; } public boolean disabled() { return disabled; } }