/*
* 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.List;
import java.util.Map.Entry;
import com.addthis.bundle.value.ValueFactory;
import com.addthis.bundle.value.ValueObject;
import com.addthis.codec.annotations.FieldConfig;
import com.addthis.codec.codables.SuperCodable;
import com.addthis.hydra.data.tree.DataTreeNode;
import com.addthis.hydra.data.tree.DataTreeNodeUpdater;
import com.addthis.hydra.data.tree.ReadTreeNode;
import com.addthis.hydra.data.tree.TreeDataParameters;
import com.addthis.hydra.data.tree.TreeNodeData;
import com.addthis.hydra.data.tree.TreeNodeDataDeferredOperation;
import com.addthis.hydra.data.util.KeyTopper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataLimitTop extends TreeNodeData<DataLimitTop.Config> implements SuperCodable {
private static final Logger log = LoggerFactory.getLogger(DataLimitTop.class);
/**
* This data attachment <span class="hydra-summary">limits child nodes to top values</span>.
* <p/>
* <p>Job Configuration Example:</p>
* <pre>
* {const:"all-urls", data.limit.limit-top.size:5000}
* </pre>
*
*/
public static final class Config extends TreeDataParameters<DataLimitTop> {
/**
* Maximum number of child nodes allowed.
* This field is required.
*/
@FieldConfig(codable = true, required = true)
private int size;
/**
* If true then output debugging information. Default is false.
*/
@FieldConfig(codable = true)
private boolean test;
@Override
public DataLimitTop newInstance() {
DataLimitTop dc = new DataLimitTop();
dc.size = size;
dc.test = test;
dc.top = new KeyTopper().init().setLossy(true);
return dc;
}
}
@FieldConfig(codable = true, required = true)
private KeyTopper top;
@FieldConfig(codable = true)
private int size;
@FieldConfig(codable = true)
private boolean test;
@Override
public boolean updateChildData(DataTreeNodeUpdater state, DataTreeNode tn, Config conf) {
return false;
}
@Override
public boolean updateParentData(DataTreeNodeUpdater state, DataTreeNode parentNode,
DataTreeNode childNode,
List<TreeNodeDataDeferredOperation> deferredOps) {
String offer = childNode.getName();
if (offer == null) {
if (test) {
log.warn("UPD null offer " + childNode);
}
return false;
}
synchronized (top) {
// will be a nullop if the offered key does not already exist in the top
top.incrementExisting(offer);
}
return true;
}
public static class DataLimitTopDeferredOperation extends TreeNodeDataDeferredOperation {
final boolean test;
final String dropped;
final DataTreeNode parentNode;
final DataTreeNode childNode;
DataLimitTopDeferredOperation(boolean test, String dropped,
DataTreeNode parentNode,
DataTreeNode childNode) {
this.test = test;
this.dropped = dropped;
this.parentNode = parentNode;
this.childNode = childNode;
}
@Override
public void run() {
DataTreeNode probe = test ? parentNode.getNode(dropped) : null;
if (parentNode.deleteNode(dropped)) {
if (test) {
DataTreeNode find = parentNode.getNode(dropped);
if (find != null && find == probe) {
log.warn("UPD drop failure (" + parentNode + "->" + probe + ") --> " + find);
}
}
} else if (test) {
log.warn("UPD drop failure, no such node (" + parentNode + "->" + childNode + ") on '" + dropped + "'");
}
}
}
@Override
public boolean updateParentNewChild(DataTreeNodeUpdater state, DataTreeNode parentNode,
DataTreeNode childNode,
List<TreeNodeDataDeferredOperation> deferredOps) {
String offer = childNode.getName();
if (offer == null) {
if (test) {
log.warn("UPD null offer " + childNode);
}
return false;
}
String dropped;
synchronized (top) {
// this increment will create the node in the top if it doesn't
// already exist with a starting count
// equal to the minValue in the top + 1. If this put forces
// something out of the top that value
// will be returned so we can remove it from the tree
dropped = top.increment(offer, size);
}
if (test && log.isDebugEnabled()) {
log.debug("UPD " + parentNode + " offer=" + offer + " drop=" + dropped + " size=" + top.size());
}
if (dropped != null) {
if (offer.equals(dropped)) {
log.warn("UPD offer equals drop (" + parentNode + "->" + childNode + ") on '" + offer + "'");
return false;
}
deferredOps.add(new DataLimitTopDeferredOperation(test, dropped, parentNode, childNode));
}
return true;
}
@Override
public ValueObject getValue(String key) {
if (key != null && key.length() > 0) {
if (key.equals("size")) {
return ValueFactory.create(top.size());
}
try {
if (key.charAt(0) == 'v') {
int pos = Integer.parseInt(key.substring(1));
return pos <= top.size() ? ValueFactory.create(top.getSortedEntries()[pos - 1].getValue()) : null;
}
if (key.charAt(0) == 'k') {
key = key.substring(1);
}
int pos = Integer.parseInt(key);
return pos <= top.size() ? ValueFactory.create(top.getSortedEntries()[pos - 1].getKey()) : null;
} catch (Exception e) {
return ValueFactory.create(e.toString());
}
}
return null;
}
@Override
public List<DataTreeNode> getNodes(DataTreeNode parent, String key) {
if (key == null) {
return null;
}
if (key.equals("hit") || key.equals("node")) {
KeyTopper map = top;
Entry<String, Long>[] top = map.getSortedEntries();
ArrayList<DataTreeNode> ret = new ArrayList<>(top.length);
for (Entry<String, Long> e : top) {
DataTreeNode node = parent.getNode(e.getKey());
if (node != null) {
ret.add(node);
}
}
return ret;
} else if (key.equals("vhit")) {
Entry<String, Long>[] list = top.getSortedEntries();
ArrayList<DataTreeNode> ret = new ArrayList<>(list.length);
for (Entry<String, Long> e : list) {
ret.add(new VirtualTreeNode(e.getKey(), e.getValue()));
}
return ret;
} else if (key.equals("phit")) {
Entry<String, Long>[] list = top.getSortedEntries();
ArrayList<DataTreeNode> ret = new ArrayList<>(list.length);
for (Entry<String, Long> e : list) {
DataTreeNode node = parent.getNode(e.getKey());
if (node != null) {
node = ((ReadTreeNode) node).getCloneWithCount(e.getValue());
ret.add(node);
}
}
return ret;
}
return null;
}
@Override
public void postDecode() {
// log.warn("deserialized "+this+" with "+top.size()+" elements --> "+top);
}
@Override
public void preEncode() {
// log.warn("serializing "+this+" with "+top.size()+" elements --> "+top);
}
}