blob: 55e1c062b060d7a4c9ab419b0acd24f68dd5dfa3 [file] [log] [blame]
/**
*
*/
package uk.org.taverna.scufl2.validation.structural;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import uk.org.taverna.scufl2.api.container.WorkflowBundle;
import uk.org.taverna.scufl2.api.core.DataLink;
import uk.org.taverna.scufl2.api.core.Processor;
import uk.org.taverna.scufl2.api.core.Workflow;
import uk.org.taverna.scufl2.api.iterationstrategy.CrossProduct;
import uk.org.taverna.scufl2.api.iterationstrategy.DotProduct;
import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyNode;
import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyStack;
import uk.org.taverna.scufl2.api.iterationstrategy.IterationStrategyTopNode;
import uk.org.taverna.scufl2.api.iterationstrategy.PortNode;
import uk.org.taverna.scufl2.api.port.InputProcessorPort;
import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
import uk.org.taverna.scufl2.api.profiles.Profile;
import uk.org.taverna.scufl2.validation.ValidationProblem;
/**
* @author alanrw
*
*/
public final class StructuralValidator {
private static enum ProcessorCheckStatus { COULD_NOT_CHECK, PASSED, FAILED };
protected ThreadLocal<ValidatorState> validatorState = new ThreadLocal<ValidatorState>() {
@Override
protected ValidatorState initialValue() {
return new ValidatorState();
};
};
public void checkStructure(WorkflowBundle bundle, StructuralValidationListener eventListener) {
validatorState.get().setEventListener(eventListener);
validatorState.get().setWorkflowBundle(bundle);
for (Workflow w : bundle.getWorkflows()) {
checkStructure(w);
}
}
public ValidatorState getValidatorState() {
return validatorState.get();
}
public void checkStructure(Workflow workflow, StructuralValidationListener eventListener) {
validatorState.get().setEventListener(eventListener);
checkStructure(workflow);
}
private void checkStructure(Workflow workflow) {
validatorState.get().setWorkflow(workflow);
validateWorkflow();
}
private void validateWorkflow() {
clearWorkflowData();
rememberDataLinkConnections();
inheritDataLinkDepthsFromWorkflowInputPorts();
checkProcessors();
checkWorkflowOutputPorts();
checkCompleteness();
}
private void clearWorkflowData() {
validatorState.get().clearWorkflowData();
}
private void checkCompleteness() {
Workflow w = validatorState.get().getWorkflow();
if (w.getProcessors().isEmpty() && w.getOutputPorts().isEmpty()) {
validatorState.get().getEventListener().incompleteWorkflow(w);
// validatorState.get().addIncompleteWorkflow(w);
}
}
private void checkProcessors() {
Workflow workflow = validatorState.get().getWorkflow();
List<Processor> failedProcessors = new ArrayList<Processor>();
List<Processor> unresolvedProcessors = new ArrayList<Processor>();
unresolvedProcessors.addAll(workflow.getProcessors());
boolean finished = false;
while (!finished) {
// We're finished unless something happens later
finished = true;
// Keep a list of processors to remove from the unresolved list
// because they've been resolved properly
List<Processor> removeValidated = new ArrayList<Processor>();
// Keep another list of those that have failed
List<Processor> removeFailed = new ArrayList<Processor>();
for (Processor p : unresolvedProcessors) {
validatorState.get().setProcessor(p);
ProcessorCheckStatus entityValid = checkProcessor();
switch (entityValid) {
case PASSED:
validatorState.get().getEventListener().passedProcessor(p);
// validatorState.get().addPassedProcessor(p);
removeValidated.add(p);
break;
case FAILED:
validatorState.get().getEventListener().failedProcessorAdded(p);
// validatorState.get().failCurrentProcessor();
removeFailed.add(p);
break;
case COULD_NOT_CHECK:
break;
}
}
/**
* Remove validated and failed items from the pending lists. If
* anything was removed because it validated okay then we're not
* finished yet and should reset the boolean finished flag
*/
if (!removeValidated.isEmpty()) {
unresolvedProcessors.removeAll(removeValidated);
finished = false;
}
unresolvedProcessors.removeAll(failedProcessors);
}
for (Processor p : unresolvedProcessors) {
validatorState.get().getEventListener().unresolvedProcessorAdded(p);
}
// validatorState.get().addUnresolvedProcessors(unresolvedProcessors);
}
private void checkWorkflowOutputPorts() {
for (OutputWorkflowPort owp : validatorState.get().getWorkflow().getOutputPorts()) {
DataLink mainIncomingLink = validatorState.get().getMainIncomingDataLink(owp);
if (mainIncomingLink == null) {
validatorState.get().getEventListener().missingMainIncomingLink(owp);
// validatorState.get().addMissingMainIncomingDataLink(owp);
}
Integer dataLinkResolvedDepth = validatorState.get().getDataLinkResolvedDepth(mainIncomingLink);
if (dataLinkResolvedDepth == null) {
validatorState.get().getEventListener().unresolvedOutput(owp);
// validatorState.get().addUnresolvedOutput(owp);
return;
}
// int granularDepth = mainIncomingLink.getSource().getGranularDepth();
Integer portResolvedDepth = dataLinkResolvedDepth + (validatorState.get().isMergedPort(owp) ? 1 : 0);
validatorState.get().getEventListener().depthResolution(owp, portResolvedDepth);
validatorState.get().setPortResolvedDepth(owp, portResolvedDepth);
// dopi.setDepths(resolvedDepth, granularDepth);
}
}
private ProcessorCheckStatus checkProcessor() {
ProcessorCheckStatus result = ProcessorCheckStatus.COULD_NOT_CHECK;
Processor p = validatorState.get().getProcessor();
Map<InputProcessorPort, Integer> inputDepths = new HashMap<InputProcessorPort, Integer>();
// Check whether all our input ports have inbound links
for (InputProcessorPort input : p.getInputPorts()) {
DataLink mainIncomingLink = validatorState.get().getMainIncomingDataLink(input);
if (mainIncomingLink == null) {
validatorState.get().getEventListener().missingMainIncomingLink(input);
// validatorState.get().addMissingMainIncomingDataLink(input);
return ProcessorCheckStatus.FAILED;
}
Integer dataLinkResolvedDepth = validatorState.get().getDataLinkResolvedDepth(mainIncomingLink);
if (dataLinkResolvedDepth == null) {
return ProcessorCheckStatus.COULD_NOT_CHECK;
}
Integer resolvedDepth = dataLinkResolvedDepth + (validatorState.get().isMergedPort(input) ? 1 : 0);
validatorState.get().getEventListener().depthResolution(input, resolvedDepth);
validatorState.get().setPortResolvedDepth(input, resolvedDepth);
inputDepths.put(input, resolvedDepth);
}
Integer resultWrappingDepth = calculateResultWrappingDepth(inputDepths);
if (resultWrappingDepth == null) {
return ProcessorCheckStatus.FAILED;
}
for (OutputProcessorPort output : p.getOutputPorts()) {
Integer portDepth = output.getDepth();
Integer resolvedDepth = portDepth + resultWrappingDepth;
validatorState.get().getEventListener().depthResolution(output, resolvedDepth);
validatorState.get().setPortResolvedDepth(output, resolvedDepth);
for (DataLink dl : validatorState.get().getOutgoingDataLinks(output)) {
validatorState.get().getEventListener().depthResolution(dl, resolvedDepth);
validatorState.get().setDataLinkResolvedDepth(dl, resolvedDepth);
}
}
return ProcessorCheckStatus.PASSED;
}
Integer calculateResultWrappingDepth(Map<InputProcessorPort, Integer> inputDepths) {
Processor p = validatorState.get().getProcessor();
IterationStrategyStack iss = p.getIterationStrategyStack();
if (iss == null) {
validatorState.get().getEventListener().missingIterationStrategyStack(p);
// validatorState.get().addMissingIterationStrategyStack(p);
validatorState.get().getEventListener().failedProcessorAdded(p);
// validatorState.get().failCurrentProcessor();
return null;
}
if (iss.isEmpty()) {
return 0;
}
IterationStrategyTopNode iterationStrategyTopNode = iss.get(0);
Integer depth = getIterationDepth(iterationStrategyTopNode, inputDepths);
if (depth == null) {
return null;
}
IterationStrategyTopNode previousNode = iterationStrategyTopNode;
for (int index = 1; index < iss.size(); index++) {
// Construct the input depths for the staged iteration strategies
// after the first one by looking at the previous iteration
// strategy's desired cardinalities on its input ports.
Map<InputProcessorPort, Integer> stagedInputDepths = getDesiredCardinalities(previousNode);
iterationStrategyTopNode = iss.get(index);
Integer nodeDepth = getIterationDepth(iterationStrategyTopNode, stagedInputDepths);
if (nodeDepth == null) {
return null;
}
depth += nodeDepth;
previousNode = iterationStrategyTopNode;
}
return depth;
}
private Map<InputProcessorPort, Integer> getDesiredCardinalities(
IterationStrategyTopNode iterationStrategyTopNode) {
Map<InputProcessorPort, Integer> desiredCardinalities = new HashMap<InputProcessorPort, Integer>();
fillInDesiredCardinalities(iterationStrategyTopNode, desiredCardinalities);
return desiredCardinalities;
}
private void fillInDesiredCardinalities(
IterationStrategyNode iterationStrategyNode,
Map<InputProcessorPort, Integer> desiredCardinalities) {
if (iterationStrategyNode instanceof IterationStrategyTopNode) {
for (IterationStrategyNode subNode : (IterationStrategyTopNode) iterationStrategyNode) {
fillInDesiredCardinalities (subNode, desiredCardinalities);
}
} else if (iterationStrategyNode instanceof PortNode) {
PortNode portNode = (PortNode) iterationStrategyNode;
desiredCardinalities.put(portNode.getInputProcessorPort(), portNode.getDesiredDepth());
}
}
public Integer getIterationDepth(IterationStrategyNode iterationStrategyNode, Map<InputProcessorPort, Integer> inputDepths) {
if (iterationStrategyNode instanceof CrossProduct) {
return getCrossProductIterationDepth ((CrossProduct) iterationStrategyNode, inputDepths);
}
if (iterationStrategyNode instanceof DotProduct) {
return getDotProductIterationDepth ((DotProduct) iterationStrategyNode, inputDepths);
}
if (iterationStrategyNode instanceof PortNode) {
return getPortNodeIterationDepth((PortNode) iterationStrategyNode, inputDepths);
}
validatorState.get().getEventListener().unrecognizedIterationStrategyNode(iterationStrategyNode);
// validatorState.get().addUnrecognizedIterationStrategyNode(iterationStrategyNode);
validatorState.get().getEventListener().failedProcessorAdded(validatorState.get().getProcessor());
// validatorState.get().failCurrentProcessor();
return null;
}
private Integer getPortNodeIterationDepth(PortNode portNode,
Map<InputProcessorPort, Integer> inputDepths) {
int myInputDepth = inputDepths.get(portNode.getInputProcessorPort());
int depthMismatch = myInputDepth - portNode.getDesiredDepth();
return (depthMismatch > 0 ? depthMismatch : 0);
}
public Integer getDotProductIterationDepth(
DotProduct dotProduct,
Map<InputProcessorPort, Integer> inputDepths) {
if (dotProduct.isEmpty()) {
validatorState.get().getEventListener().emptyDotProduct(dotProduct);
// validatorState.get().addEmptyDotProduct(dotProduct);
validatorState.get().getEventListener().failedProcessorAdded(validatorState.get().getProcessor());
// validatorState.get().failCurrentProcessor();
return null;
}
Integer depth = getIterationDepth(dotProduct.get(0), inputDepths);
if (depth == null) {
return null;
}
for (IterationStrategyNode childNode : dotProduct) {
Integer childNodeDepth = getIterationDepth(childNode, inputDepths);
if (childNodeDepth == null) {
return null;
}
if (!childNodeDepth.equals(depth)) {
validatorState.get().getEventListener().dotProductIterationMismatch(dotProduct);
// validatorState.get().addDotProductIterationMismatch(dotProduct);
validatorState.get().getEventListener().failedProcessorAdded(validatorState.get().getProcessor());
// validatorState.get().failCurrentProcessor();
return null;
}
}
return depth;
}
private Integer getCrossProductIterationDepth(
CrossProduct crossProduct,
Map<InputProcessorPort, Integer> inputDepths) {
if (crossProduct.isEmpty()) {
validatorState.get().getEventListener().emptyCrossProduct(crossProduct);
// validatorState.get().addEmptyCrossProduct(crossProduct);
validatorState.get().getEventListener().failedProcessorAdded(validatorState.get().getProcessor());
// validatorState.get().failCurrentProcessor();
return null;
}
int temp = 0;
for (IterationStrategyNode child : crossProduct) {
Integer childNodeDepth = getIterationDepth(child, inputDepths);
if (childNodeDepth == null) {
return null;
}
temp += childNodeDepth;
}
return temp;
}
private void rememberDataLinkConnections() {
Workflow workflow = validatorState.get().getWorkflow();
for (DataLink dl : workflow.getDataLinks()) {
validatorState.get().getEventListener().dataLinkSender(dl);
validatorState.get().rememberDataLinkSender(dl);
validatorState.get().getEventListener().dataLinkReceiver(dl);
validatorState.get().rememberDataLinkReceiver(dl);
}
}
private void inheritDataLinkDepthsFromWorkflowInputPorts() {
Workflow workflow = validatorState.get().getWorkflow();
for (InputWorkflowPort iwp : workflow.getInputPorts()) {
Integer iwpDepth = iwp.getDepth();
validatorState.get().getEventListener().depthResolution(iwp, iwpDepth);
validatorState.get().setPortResolvedDepth(iwp, iwpDepth);
for (DataLink dl : validatorState.get().getOutgoingDataLinks(iwp)) {
validatorState.get().getEventListener().depthResolution(dl, iwpDepth);
validatorState.get().setDataLinkResolvedDepth(dl, iwpDepth);
}
}
}
}