package eu.dnetlib.iis.wf.export.actionmanager.sequencefile;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.log4j.Logger;
import eu.dnetlib.actionmanager.actions.AtomicAction;
import eu.dnetlib.iis.common.OrderedProperties;
import eu.dnetlib.iis.common.java.PortBindings;
import eu.dnetlib.iis.common.java.Process;
import eu.dnetlib.iis.common.java.io.FileSystemPath;
import eu.dnetlib.iis.common.java.io.SequenceFileTextValueReader;
import eu.dnetlib.iis.common.java.porttype.AnyPortType;
import eu.dnetlib.iis.common.java.porttype.PortType;
/**
* Sequence file testing consumer. Expects {@link Text} values at input.
* Requirements are provided as comma separated list of properties files
* holding requirements for each individual input object.
* Properties keys are paths to the fields we want to test in given object,
* properties values are expected values (with special markup for null: $NULL$).
* This means number of input records in sequence file should be equal to the number of provided properties files.
*
* @author mhorst
*/
public class TestingConsumer implements Process {
public static final String EXPECTATION_FILE_PATHS = "expectation_file_paths";
public static final String PORT_INPUT = "seqfile";
public static final String NULL_VALUE_INDICATOR = "$NULL$";
private final FieldAccessor accessor;
private final static Logger log = Logger.getLogger(TestingConsumer.class);
private final Map<String, PortType> inputPorts = new HashMap<String, PortType>();
//------------------------ CONSTRUCTORS --------------------------
public TestingConsumer() {
inputPorts.put(PORT_INPUT, new AnyPortType());
accessor = new FieldAccessor();
accessor.registerDecoder("targetValue", new OafFieldDecoder());
}
//------------------------ LOGIC ---------------------------------
@Override
public Map<String, PortType> getInputPorts() {
return inputPorts;
}
@Override
public Map<String, PortType> getOutputPorts() {
return Collections.emptyMap();
}
@Override
public void run(PortBindings portBindings, Configuration configuration, Map<String, String> parameters)
throws Exception {
String propertiesPathsCSV = parameters.get(EXPECTATION_FILE_PATHS);
if (StringUtils.isEmpty(propertiesPathsCSV)) {
throw new Exception("no " + EXPECTATION_FILE_PATHS + " property value provided, "
+ "output requirements were not specified!");
}
FileSystem fs = FileSystem.get(configuration);
Path inputPath = portBindings.getInput().get(PORT_INPUT);
if (!fs.exists(inputPath)) {
throw new Exception(inputPath + " hdfs location does not exist!");
}
try (SequenceFileTextValueReader it = new SequenceFileTextValueReader(
new FileSystemPath(fs, inputPath))) {
int actionsCount = 0;
String[] recordsSpecs = StringUtils.split(propertiesPathsCSV, ',');
while (it.hasNext()) {
AtomicAction action = AtomicAction.fromJSON(it.next().toString());
actionsCount++;
if (actionsCount > recordsSpecs.length) {
throw new Exception("got more records than expected: " + "unable to verify record no " + actionsCount
+ ", no field specification provided! Record contents: " + action);
} else {
evaluateExpectations(recordsSpecs[actionsCount - 1], action);
}
}
if (actionsCount < recordsSpecs.length) {
throw new Exception(
"records count mismatch: " + "got: " + actionsCount + " expected: " + recordsSpecs.length);
}
}
}
//------------------------ PRIVATE ---------------------------------
private void evaluateExpectations(String currentSpecLocation, AtomicAction action) throws Exception {
log.info("output specification location: " + currentSpecLocation);
Properties specProps = new OrderedProperties();
specProps.load(TestingConsumer.class.getResourceAsStream(currentSpecLocation.trim()));
Iterator<Entry<Object,Object>> propsIter = specProps.entrySet().iterator();
while (propsIter.hasNext()) {
Entry<Object,Object> entry = propsIter.next();
Object currentValue = accessor.getValue((String)entry.getKey(), action);
if ((currentValue != null && !entry.getValue().equals(currentValue.toString()))
|| (currentValue == null && !NULL_VALUE_INDICATOR.equals(entry.getValue()))) {
throw new Exception("invalid field value for path: " + entry.getKey()
+ ", expected: '" + entry.getValue() + "', "
+ "got: '" + currentValue + "' Full object content: " + action);
}
}
}
}