blob: 228a91e2bf0018b31a350ec529bbed600cac450b [file] [log] [blame]
/**
*
*/
package org.apache.taverna.scufl2.validation.correctness;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*
*/
import java.net.URI;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.taverna.scufl2.api.activity.Activity;
import org.apache.taverna.scufl2.api.common.Child;
import org.apache.taverna.scufl2.api.common.Configurable;
import org.apache.taverna.scufl2.api.common.Named;
import org.apache.taverna.scufl2.api.common.NamedSet;
import org.apache.taverna.scufl2.api.common.Ported;
import org.apache.taverna.scufl2.api.common.Root;
import org.apache.taverna.scufl2.api.common.Typed;
import org.apache.taverna.scufl2.api.common.WorkflowBean;
import org.apache.taverna.scufl2.api.configurations.Configuration;
import org.apache.taverna.scufl2.api.container.WorkflowBundle;
import org.apache.taverna.scufl2.api.core.BlockingControlLink;
import org.apache.taverna.scufl2.api.core.ControlLink;
import org.apache.taverna.scufl2.api.core.DataLink;
import org.apache.taverna.scufl2.api.core.Processor;
import org.apache.taverna.scufl2.api.core.Workflow;
import org.apache.taverna.scufl2.api.iterationstrategy.CrossProduct;
import org.apache.taverna.scufl2.api.iterationstrategy.DotProduct;
import org.apache.taverna.scufl2.api.iterationstrategy.IterationStrategyNode;
import org.apache.taverna.scufl2.api.iterationstrategy.IterationStrategyParent;
import org.apache.taverna.scufl2.api.iterationstrategy.IterationStrategyStack;
import org.apache.taverna.scufl2.api.iterationstrategy.IterationStrategyTopNode;
import org.apache.taverna.scufl2.api.iterationstrategy.PortNode;
import org.apache.taverna.scufl2.api.port.AbstractDepthPort;
import org.apache.taverna.scufl2.api.port.AbstractGranularDepthPort;
import org.apache.taverna.scufl2.api.port.ActivityPort;
import org.apache.taverna.scufl2.api.port.InputActivityPort;
import org.apache.taverna.scufl2.api.port.InputPort;
import org.apache.taverna.scufl2.api.port.InputProcessorPort;
import org.apache.taverna.scufl2.api.port.InputWorkflowPort;
import org.apache.taverna.scufl2.api.port.OutputActivityPort;
import org.apache.taverna.scufl2.api.port.OutputPort;
import org.apache.taverna.scufl2.api.port.OutputProcessorPort;
import org.apache.taverna.scufl2.api.port.OutputWorkflowPort;
import org.apache.taverna.scufl2.api.port.Port;
import org.apache.taverna.scufl2.api.port.ProcessorPort;
import org.apache.taverna.scufl2.api.port.ReceiverPort;
import org.apache.taverna.scufl2.api.port.SenderPort;
import org.apache.taverna.scufl2.api.port.WorkflowPort;
import org.apache.taverna.scufl2.api.profiles.ProcessorBinding;
import org.apache.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
import org.apache.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
import org.apache.taverna.scufl2.api.profiles.ProcessorPortBinding;
import org.apache.taverna.scufl2.api.profiles.Profile;
import com.fasterxml.jackson.databind.JsonNode;
/**
* @author alanrw
*/
public class CorrectnessVisitor extends DispatchingVisitor {
private final boolean checkComplete;
private final CorrectnessValidationListener listener;
public CorrectnessVisitor(boolean checkComplete, CorrectnessValidationListener listener) {
this.checkComplete = checkComplete;
this.listener = listener;
}
public boolean isCheckComplete() {
return checkComplete;
}
private WorkflowBean peekPath() {
return getCurrentPath().peek();
}
public <T> T findAncestral(Child<?> bean, Class<T> class1) {
T result = null;
for (WorkflowBean parent = bean.getParent(); parent != null; parent = ((Child<?>) parent)
.getParent()) {
if (class1.isInstance(parent))
return class1.cast(parent);
if (!(parent instanceof Child))
return null;
}
return result;
}
@Override
public void visitAbstractDepthPort(AbstractDepthPort bean) {
Integer depth = bean.getDepth();
if (depth != null && depth < 0)
listener.negativeValue(bean, "depth", depth);
if (checkComplete && depth == null)
listener.nullField(bean, "depth");
}
@Override
public void visitAbstractGranularDepthPort(AbstractGranularDepthPort bean) {
Integer granularDepth = bean.getGranularDepth();
if (granularDepth != null) {
if (granularDepth < 0)
listener.negativeValue(bean, "granularDepth", granularDepth);
Integer depth = bean.getDepth();
if (depth != null)
if (granularDepth > depth)
listener.incompatibleGranularDepth(bean, depth,
granularDepth);
}
if (checkComplete && granularDepth == null)
listener.nullField(bean, "granularDepth");
}
@Override
public void visitActivity(Activity bean) {
// All checks are covered by those in Named, Typed, Child.
}
@Override
public void visitActivityPort(ActivityPort bean) {
// All checks are covered by those in Named and Child
}
@Override
public void visitBlockingControlLink(BlockingControlLink bean) {
// Also checks from Child
Workflow parent = bean.getParent();
Processor block = bean.getBlock();
Processor untilFinished = bean.getUntilFinished();
// Check the block and untilFinished processors are in the same workflow
if (block != null) {
Workflow blockParent = block.getParent();
if ((parent == null) || !parent.equals(blockParent))
listener.outOfScopeValue(bean, "block", block);
}
if (untilFinished != null) {
Workflow untilFinishedParent = untilFinished.getParent();
if ((parent == null) || !parent.equals(untilFinishedParent))
listener.outOfScopeValue(bean, "untilFinished", untilFinished);
}
// Check the block and untilFinished processors are specified
if (checkComplete) {
if (block == null)
listener.nullField(bean, "block");
if (untilFinished == null)
listener.nullField(bean, "untilFinished");
}
}
@Override
public void visitChild(Child<?> bean) {
WorkflowBean p = bean.getParent();
try {
if (p != null) {
WorkflowBean up = peekPath();
if ((up != null) && (up != p))
listener.wrongParent(bean);
}
} catch (EmptyStackException e) {
// Nothing
}
if (checkComplete && p == null)
listener.nullField(bean, "parent");
}
@Override
public void visitConfigurable(Configurable bean) {
// Are there any checks that it is actually configured?
}
@Override
public void visitConfiguration(Configuration bean) {
Configurable configures = bean.getConfigures();
JsonNode json = bean.getJson();
@SuppressWarnings("unused")
URI type = null;
if (configures != null && configures instanceof Typed)
type = ((Typed) configures).getType();
// Correct check cannot be completed unless property descriptions are available
// URI configurationType = bean.getConfigurableType();
// if ((configuresType != null) && (configurationType != null))
// if (!configuresType.equals(configurationType))
// listener.mismatchConfigurableType(bean, configures);
if (checkComplete) {
if (configures == null)
listener.nullField(bean, "configures");
if (json == null)
listener.nullField(bean, "json");
// TODO Check that the PropertyResource is complete
}
}
@Override
public void visitControlLink(ControlLink bean) {
// All done in Child or BlockingControlLink
}
@Override
public void visitCrossProduct(CrossProduct bean) {
// All done in IterationStrategyTopNode and Child
}
@Override
public void visitDataLink(DataLink bean) {
ReceiverPort sendsTo = bean.getSendsTo();
SenderPort receivesFrom = bean.getReceivesFrom();
Workflow parent = bean.getParent();
if (sendsTo != null) {
Workflow sendsToWorkflow = findAncestral((Child<?>) sendsTo,
Workflow.class);
if ((parent == null) || !parent.equals(sendsToWorkflow))
listener.outOfScopeValue(bean, "sendsTo", sendsTo);
}
if (receivesFrom != null) {
Workflow receivesFromWorkflow = findAncestral((Child<?>) receivesFrom,
Workflow.class);
if ((parent == null) || !parent.equals(receivesFromWorkflow))
listener.outOfScopeValue(bean, "receivesFrom", receivesFrom);
}
Integer mergePosition = bean.getMergePosition();
if (mergePosition != null && mergePosition < 0)
listener.negativeValue(bean, "mergePosition", mergePosition);
// How to check mergePosition
if (checkComplete) {
if (sendsTo == null)
listener.nullField(bean, "sendsTo");
if (receivesFrom == null)
listener.nullField(bean, "receivesFrom");
}
}
@Override
public void visitDotProduct(DotProduct bean) {
// All done in IterationStrategyTopNode and Child
}
@Override
public void visitInputActivityPort(InputActivityPort bean) {
// All done in Child, Named and Configurable
}
@Override
public void visitInputPort(InputPort bean) {
// All done in Named and Configurable
}
@Override
public void visitInputProcessorPort(InputProcessorPort bean) {
// All done in superclasses and interfaces
}
@Override
public void visitInputWorkflowPort(InputWorkflowPort bean) {
// All done in superclasses and interfaces
}
@Override
public void visitIterationStrategyNode(IterationStrategyNode bean) {
// All done in superclasses and interfaces
}
@Override
public void visitIterationStrategyParent(IterationStrategyParent bean) {
// Nothing to do
}
@Override
public void visitIterationStrategyStack(IterationStrategyStack bean) {
Processor parent = bean.getParent();
if (parent != null && checkComplete) {
Set<Port> mentionedPorts = new HashSet<>();
for (IterationStrategyTopNode node : bean)
mentionedPorts.addAll(getReferencedPorts(node));
NamedSet<InputProcessorPort> inputPorts = parent.getInputPorts();
if (inputPorts != null)
for (Port p : inputPorts)
if (!mentionedPorts.contains(p))
listener.portMissingFromIterationStrategyStack(p, bean);
}
}
@Override
public void visitIterationStrategyTopNode(IterationStrategyTopNode bean) {
if (checkComplete&&bean.isEmpty())
listener.emptyIterationStrategyTopNode(bean);
Map<Port, IterationStrategyNode> portsSoFar = new HashMap<>();
for (IterationStrategyNode subNode : bean)
if (subNode instanceof PortNode) {
InputProcessorPort port = ((PortNode) subNode).getInputProcessorPort();
if (port != null) {
if (portsSoFar.containsKey(port))
listener.portMentionedTwice(portsSoFar.get(port), subNode);
else
portsSoFar.put(port, subNode);
}
} else
for (Port p : getReferencedPorts((IterationStrategyTopNode) subNode))
if (portsSoFar.containsKey(p))
listener.portMentionedTwice(portsSoFar.get(p), subNode);
else
portsSoFar.put(p, subNode);
}
private Set<Port> getReferencedPorts(IterationStrategyTopNode bean) {
Set<Port> result = new HashSet<>();
for (IterationStrategyNode subNode : bean)
if (subNode instanceof PortNode) {
InputProcessorPort port = ((PortNode) subNode).getInputProcessorPort();
if (port != null)
result.add(port);
} else
result.addAll(getReferencedPorts((IterationStrategyTopNode) subNode));
return result;
}
@Override
public void visitNamed(Named bean) {
// What are the constraints upon the string used as the name?
if (checkComplete) {
String name = bean.getName();
if ((name == null) || name.isEmpty())
listener.nullField(bean, "name");
}
}
@Override
public void visitOutputActivityPort(OutputActivityPort bean) {
// All done in superclasses and interfaces
}
@Override
public void visitOutputPort(OutputPort bean) {
// All done in superclasses and interfaces
}
@Override
public void visitOutputProcessorPort(OutputProcessorPort bean) {
// All done in superclasses and interfaces
}
@Override
public void visitOutputWorkflowPort(OutputWorkflowPort bean) {
// All done in superclasses and interfaces
}
@Override
public void visitPort(Port bean) {
// All done in superclasses and interfaces
}
@Override
public void visitPortNode(PortNode bean) {
InputProcessorPort inputProcessorPort = bean.getInputProcessorPort();
Integer desiredDepth = bean.getDesiredDepth();
if (desiredDepth != null && desiredDepth < 0)
listener.negativeValue(bean, "desiredDepth", desiredDepth);
if (inputProcessorPort != null) {
Processor ancestralProcessor = findAncestral(bean, Processor.class);
Processor portAncestralProcessor = findAncestral(
inputProcessorPort, Processor.class);
if ((ancestralProcessor == null)
|| !ancestralProcessor.equals(portAncestralProcessor))
listener.outOfScopeValue(bean, "inputProcessorPort",
inputProcessorPort);
}
if (checkComplete) {
if (inputProcessorPort == null)
listener.nullField(bean, "inputProcessorPort");
if (desiredDepth == null)
listener.nullField(bean, "desiredDepth");
}
}
@Override
public void visitPorted(Ported bean) {
if (checkComplete) {
if (bean.getInputPorts() == null)
listener.nullField(bean, "inputPorts");
if (bean.getOutputPorts() == null)
listener.nullField(bean, "outputPorts");
}
}
@Override
public void visitProcessor(Processor bean) {
if (checkComplete && bean.getIterationStrategyStack() == null)
listener.nullField(bean, "iterationStrategyStack");
}
@Override
public void visitProcessorBinding(ProcessorBinding bean) {
Processor boundProcessor = bean.getBoundProcessor();
Activity boundActivity = bean.getBoundActivity();
WorkflowBundle workflowBundle = findAncestral(bean,
WorkflowBundle.class);
if (boundProcessor != null) {
WorkflowBundle boundProcessorBundle = findAncestral(boundProcessor,
WorkflowBundle.class);
if ((workflowBundle == null)
|| !workflowBundle.equals(boundProcessorBundle))
listener.outOfScopeValue(bean, "boundProcessor", boundProcessor);
}
if (boundActivity != null) {
WorkflowBundle boundActivityBundle = findAncestral(boundActivity,
WorkflowBundle.class);
if ((workflowBundle == null)
|| !workflowBundle.equals(boundActivityBundle))
listener.outOfScopeValue(bean, "boundActivity", boundActivity);
}
Integer activityPosition = bean.getActivityPosition();
if (activityPosition != null && activityPosition < 0)
listener.negativeValue(bean, "activityPosition", activityPosition);
if (checkComplete) {
if (boundProcessor == null)
listener.nullField(bean, "boundProcessor");
if (boundActivity == null)
listener.nullField(bean, "boundActivity");
// ActivityPosition can be null
if (bean.getInputPortBindings() == null)
listener.nullField(bean, "inputPortBindings");
if (bean.getOutputPortBindings() == null)
listener.nullField(bean, "outputPortBindings");
}
}
@Override
public void visitProcessorInputPortBinding(ProcessorInputPortBinding bean) {
ProcessorBinding parent = bean.getParent();
InputProcessorPort boundProcessorPort = bean.getBoundProcessorPort();
InputActivityPort boundActivityPort = bean.getBoundActivityPort();
if (parent != null) {
Processor boundProcessor = parent.getBoundProcessor();
if (boundProcessorPort != null) {
Processor boundPortProcessor = findAncestral(
boundProcessorPort, Processor.class);
if ((boundProcessor == null)
|| !boundProcessor.equals(boundPortProcessor))
listener.outOfScopeValue(bean, "boundProcessorPort",
boundProcessorPort);
}
Activity boundActivity = parent.getBoundActivity();
if (boundActivityPort != null) {
Activity boundPortActivity = findAncestral(boundActivityPort,
Activity.class);
if ((boundActivity == null)
|| !boundActivity.equals(boundPortActivity))
listener.outOfScopeValue(bean, "boundActivityPort",
boundActivityPort);
}
}
if (checkComplete) {
if (boundProcessorPort == null)
listener.nullField(bean, "boundProcessorPort");
if (boundActivityPort == null)
listener.nullField(bean, "boundActivityPort");
}
}
@Override
public void visitProcessorOutputPortBinding(ProcessorOutputPortBinding bean) {
ProcessorBinding parent = bean.getParent();
OutputProcessorPort boundProcessorPort = bean.getBoundProcessorPort();
OutputActivityPort boundActivityPort = bean.getBoundActivityPort();
if (parent != null) {
Processor boundProcessor = parent.getBoundProcessor();
if (boundProcessorPort != null) {
Processor boundPortProcessor = findAncestral(boundProcessorPort, Processor.class);
if ((boundProcessor == null) || !boundProcessor.equals(boundPortProcessor))
listener.outOfScopeValue(bean, "boundProcessorPort", boundProcessorPort);
}
Activity boundActivity = parent.getBoundActivity();
if (boundActivityPort != null) {
Activity boundPortActivity = findAncestral(boundActivityPort, Activity.class);
if ((boundActivity == null) || !boundActivity.equals(boundPortActivity))
listener.outOfScopeValue(bean, "boundActivityPort", boundActivityPort);
}
}
if (checkComplete) {
if (boundProcessorPort == null)
listener.nullField(bean, "boundProcessorPort");
if (boundActivityPort == null)
listener.nullField(bean, "boundActivityPort");
}
}
@Override
public void visitProcessorPort(ProcessorPort bean) {
// All done in superclasses and interfaces
}
@Override
public void visitProcessorPortBinding(ProcessorPortBinding<?,?> bean) {
// Done in sub-classes
}
@Override
public void visitProfile(Profile bean) {
Integer profilePosition = bean.getProfilePosition();
if (profilePosition != null && profilePosition < 0)
listener.negativeValue(bean, "profilePosition", profilePosition);
if (checkComplete) {
if (bean.getProcessorBindings() == null)
listener.nullField(bean, "processorBindings");
if (bean.getConfigurations() == null)
listener.nullField(bean, "configurations");
// It may be OK for the profilePosition to be null
if (bean.getActivities() == null)
listener.nullField(bean, "activities");
}
}
@Override
public void visitReceiverPort(ReceiverPort bean) {
// All done in superclasses and interfaces
}
@Override
public void visitRoot(Root bean) {
URI globalBaseURI = bean.getGlobalBaseURI();
if (globalBaseURI != null) {
if (!globalBaseURI.isAbsolute())
listener.nonAbsoluteURI(bean, "globalBaseURI", globalBaseURI);
else if (globalBaseURI.getScheme().equals("file"))
listener.nonAbsoluteURI(bean, "globalBaseURI", globalBaseURI);
}
if (checkComplete && globalBaseURI == null)
listener.nullField(bean, "globalBaseURI");
}
@Override
public void visitSenderPort(SenderPort bean) {
// All done in superclasses and interfaces
}
@Override
public void visitTyped(Typed bean) {
URI configurableType = bean.getType();
if (configurableType != null) {
if (!configurableType.isAbsolute())
listener.nonAbsoluteURI(bean, "configurableType", configurableType);
else if (configurableType.getScheme().equals("file"))
listener.nonAbsoluteURI(bean, "configurableType", configurableType);
}
if (checkComplete && configurableType == null)
listener.nullField(bean, "configurableType");
}
@Override
public void visitWorkflow(Workflow bean) {
Set<DataLink> dataLinks = bean.getDataLinks();
Set<ControlLink> controlLinks = bean.getControlLinks();
// ports are done in Ported
NamedSet<Processor> processors = bean.getProcessors();
URI workflowIdentifier = bean.getIdentifier();
if (workflowIdentifier != null) {
if (!workflowIdentifier.isAbsolute())
listener.nonAbsoluteURI(bean, "workflowIdentifier", workflowIdentifier);
else if (workflowIdentifier.getScheme().equals("file"))
listener.nonAbsoluteURI(bean, "workflowIdentifier", workflowIdentifier);
}
if (checkComplete) {
if (dataLinks == null)
listener.nullField(bean, "dataLinks");
if (controlLinks == null)
listener.nullField(bean, "controlLinks");
if (processors == null)
listener.nullField(bean, "processors");
if (workflowIdentifier == null)
listener.nullField(bean, "workflowIdentifier");
}
}
@Override
public void visitWorkflowBundle(WorkflowBundle bean) {
NamedSet<Profile> profiles = bean.getProfiles();
NamedSet<Workflow> workflows = bean.getWorkflows();
Workflow mainWorkflow = bean.getMainWorkflow();
Profile mainProfile = bean.getMainProfile();
if ((profiles != null) && (mainProfile != null)
&& !profiles.contains(mainProfile))
listener.outOfScopeValue(bean, "mainProfile", mainProfile);
if ((workflows != null) && (mainWorkflow != null)
&& !workflows.contains(mainWorkflow))
listener.outOfScopeValue(bean, "mainWorkflow", mainWorkflow);
if (checkComplete) {
if (profiles == null)
listener.nullField(bean, "profiles");
if (workflows == null)
listener.nullField(bean, "workflows");
}
}
@Override
public void visitWorkflowPort(WorkflowPort bean) {
// All done in superclasses and interfaces
}
}