Package eu.scape_project.planning.services.taverna.generator

Source Code of eu.scape_project.planning.services.taverna.generator.T2FlowExecutablePlanGenerator

/*******************************************************************************
* Copyright 2006 - 2014 Vienna University of Technology,
* Department of Software Technology and Interactive Systems, IFS
*
* 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 eu.scape_project.planning.services.taverna.generator;

import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;

import eu.scape_project.planning.services.myexperiment.domain.ComponentConstants;
import eu.scape_project.planning.services.myexperiment.domain.Port;
import eu.scape_project.planning.services.myexperiment.domain.WorkflowDescription;
import eu.scape_project.planning.services.taverna.generator.model.Dataflow;
import eu.scape_project.planning.services.taverna.generator.model.Datalink;
import eu.scape_project.planning.services.taverna.generator.model.InputPort;
import eu.scape_project.planning.services.taverna.generator.model.OutputPort;
import eu.scape_project.planning.services.taverna.generator.model.Workflow;
import eu.scape_project.planning.services.taverna.generator.model.processor.NestedWorkflow;
import eu.scape_project.planning.services.taverna.generator.model.processor.Processor;
import eu.scape_project.planning.services.taverna.generator.model.processor.TextConstant;

/**
* Generator for executable plans in Taverna t2flow format.
*/
public class T2FlowExecutablePlanGenerator {

    /**
     * The input source of a component.
     */
    public enum InputSource {
        /**
         * The source object of the workflow.
         */
        SOURCE_OBJECT,

        /**
         * The target object of the workflow.
         */
        TARGET_OBJECT
    }

    /**
     * The related object of a port.
     */
    public enum RelatedObject {
        /**
         * The left object of the workflow.
         */
        LEFT_OBJECT {
            /**
             * Returns a string representation of the enum.
             *
             * @return the enum as string
             */
            public String toString() {
                return ComponentConstants.VALUE_LEFT_OBJECT;
            }
        },

        /**
         * The right object of the workflow.
         */
        RIGHT_OBJECT {
            /**
             * Returns a string representation of the enum.
             *
             * @return the enum as string
             */
            public String toString() {
                return ComponentConstants.VALUE_RIGHT_OBJECT;
            }
        }
    }

    private static final String SOURCE_PORT_NAME = "source";
    private static final String TARGET_PORT_NAME = "target";

    private static final Pattern PURL_DP_MEASURE_PATTERN = Pattern
        .compile("http:\\/\\/purl\\.org\\/DP\\/quality\\/(measures)#(\\d+)");
    private static final Pattern GENERIC_MEASURE_PATTERN = Pattern.compile("http.?:\\/\\/(.+)");
    private static final Pattern PURL_DP_PORTNAME_PATTERN = Pattern.compile("(measures)_(\\d+)");

    private final String sourceMimetype;
    private final String targetMimetype;

    private Workflow workflow;

    private NestedWorkflow migration;
    private String migrationTargetPortName;

    /**
     * Creates a new Executable Plan generator as t2flow workflow.
     *
     * @param name
     *            the name of the plan
     * @param author
     *            the author of the plan
     */
    public T2FlowExecutablePlanGenerator(String name, String author) {
        this(name, author, null, null);
    }

    /**
     * Creates a new Executable Plan generator as t2flow workflow.
     *
     * @param name
     *            the name of the plan
     * @param author
     *            the author of the plan
     * @param sourceMimetype
     *            the source mimetype of the plan
     * @param targetMimetype
     *            the target mimetype of the plan
     */
    public T2FlowExecutablePlanGenerator(String name, String author, String sourceMimetype, String targetMimetype) {
        String semanticAnnotations = "<> <http://purl.org/DP/components#fits> <http://purl.org/DP/components#ExecutablePlan> .\n";

        if (sourceMimetype != null && targetMimetype != null) {
            semanticAnnotations += "<> <http://purl.org/DP/components#migrates>"
                + "[ a <http://purl.org/DP/components#MigrationPath> ;"
                + "<http://purl.org/DP/components#sourceMimetype> \"" + sourceMimetype + "\" ;"
                + "<http://purl.org/DP/components#targetMimetype> \"" + targetMimetype + "\" ] .";
        }

        this.sourceMimetype = sourceMimetype;
        this.targetMimetype = targetMimetype;
        workflow = new Workflow(name, author, semanticAnnotations);
    }

    /**
     * Adds a source port with 0 as port depth.
     *
     * @see {@link #addSourcePort(int)}
     */
    public void addSourcePort() {
        addSourcePort(0);
    }

    /**
     * Adds a source port with the provided depth.
     *
     * @param depth
     *            the port depth
     */
    public void addSourcePort(int depth) {
        InputPort inputPort = new InputPort(SOURCE_PORT_NAME, depth,
            "<>    <http://purl.org/DP/components#accepts>\n"
                + "              <http://purl.org/DP/components#SourceObject> .");
        workflow.addInputPort(inputPort);
    }

    /**
     * Adds a target port.
     */
    public void addTargetPort() {
        OutputPort outputPort = new OutputPort(TARGET_PORT_NAME,
            "<> <http://purl.org/DP/components#provides> <http://purl.org/DP/components#TargetObject> .");
        workflow.addOutputPort(outputPort);
    }

    /**
     * Adds a measure port for the provided measure.
     *
     * @param measure
     *            the measure
     */
    public void addMeasurePort(String measure) {
        String portName = createMeasurePortName(measure);
        if (portName == null) {
            throw new IllegalArgumentException("The provided measure " + measure + " is not valid");
        }
        OutputPort outputPort = new OutputPort(portName, "<> <http://purl.org/DP/components#provides> <"
            + measure + "> .");
        workflow.addOutputPort(outputPort);
    }

    /**
     * Sets the migration component.
     *
     * @param workflowDescription
     *            workflow description of the migration component
     * @param workflowContent
     *            the actual workflow content of the component
     * @param parameters
     *            map with parameters of the component
     */
    public void setMigrationComponent(WorkflowDescription workflowDescription, String workflowContent,
        Map<String, String> parameters) {
        // Dataflow
        migration = new NestedWorkflow("Migration", workflowDescription.getDataflowId());
        workflow.addProcessor(migration);

        workflowContent = convertWorkflowToNested(workflowContent);
        workflow.addDataflow(new Dataflow(workflowDescription.getDataflowId(), workflowContent));

        // Input ports
        List<Port> inputPorts = workflowDescription.getInputPorts();
        for (Port p : inputPorts) {
            if (ComponentConstants.VALUE_SOURCE_OBJECT.equals(p.getValue())) {
                migration.addInputPort(new InputPort(p.getName(), 0));
                workflow.addDatalink(new Datalink(workflow, SOURCE_PORT_NAME, migration, p.getName()));
            } else if (ComponentConstants.VALUE_PARAMETER.equals(p.getValue())) {
                migration.addInputPort(new InputPort(p.getName(), 0));
                TextConstant c = new TextConstant(p.getName(), parameters.get(p.getName()));
                workflow.addProcessor(c);
                workflow.addDatalink(new Datalink(c, "value", migration, p.getName()));
            }
        }

        // Output ports
        List<Port> outputPorts = workflowDescription.getOutputPorts();
        for (Port p : outputPorts) {
            if (ComponentConstants.VALUE_TARGET_OBJECT.equals(p.getValue())) {
                migrationTargetPortName = p.getName();
                migration.addOutputPort(new OutputPort(migrationTargetPortName));
                workflow.addDatalink(new Datalink(migration, migrationTargetPortName, workflow, TARGET_PORT_NAME));
            }
        }
    }

    /**
     * Adds a QA component.
     *
     * Adds measure ports for the provided {@code measures} to the workflow if
     * they are provided by the QA component and are not already present.
     *
     * If a port specifies the related object, only
     * {@link RelatedObject#RIGHT_OBJECT} is considered.
     *
     * @param workflowDescription
     *            workflow description of the migration component
     * @param workflowContent
     *            the actual workflow content of the component
     * @param parameters
     *            map with parameters of the component
     * @param measures
     *            measures of output ports to connect the component to
     *
     * @see {@link #addQaComponent(WorkflowDescription, String, Map, List, RelatedObject)}
     */
    public void addQaComponent(final WorkflowDescription workflowDescription, final String workflowContent,
        final Map<String, String> parameters, final List<String> measures) {
        addQaComponent(workflowDescription, workflowContent, parameters, measures, RelatedObject.RIGHT_OBJECT);
    }

    /**
     * Adds a QA component.
     *
     * Adds measure ports for the provided {@code measures} to the workflow if
     * they are provided by the QA component and are not already present.
     *
     * If an output port specifies a related object, the port will only be
     * connected if the {@code relatedObject} matches.
     *
     * The sources for the left and right inputs are set according to supported
     * mimetypes of the workflow.
     *
     * @param workflowDescription
     *            workflow description of the migration component
     * @param workflowContent
     *            the actual workflow content of the component
     * @param parameters
     *            map with parameters of the component
     * @param measures
     *            measures of output ports to connect the component to
     * @param relatedObject
     *            the related object of measures to use if present
     * @see {@link #addQaComponent(WorkflowDescription, String, InputSource, InputSource, Map, List, RelatedObject)}
     */
    public void addQaComponent(final WorkflowDescription workflowDescription, final String workflowContent,
        final Map<String, String> parameters, final List<String> measures, RelatedObject relatedObject) {

        InputSource leftSource = null;
        InputSource rightSource = null;

        if (hasMigration() && workflowDescription.acceptsMimetypes(sourceMimetype, targetMimetype)) {
            leftSource = InputSource.SOURCE_OBJECT;
            rightSource = InputSource.TARGET_OBJECT;
        } else if (hasMigration() && workflowDescription.acceptsMimetypes(targetMimetype, sourceMimetype)) {
            leftSource = InputSource.TARGET_OBJECT;
            rightSource = InputSource.SOURCE_OBJECT;
        } else if (workflowDescription.acceptsLeftMimetype(sourceMimetype)) {
            leftSource = InputSource.SOURCE_OBJECT;
        } else if (workflowDescription.acceptsRightMimetype(sourceMimetype)) {
            rightSource = InputSource.SOURCE_OBJECT;
        } else if (hasMigration() && workflowDescription.acceptsLeftMimetype(targetMimetype)) {
            leftSource = InputSource.TARGET_OBJECT;
        } else if (hasMigration() && workflowDescription.acceptsRightMimetype(targetMimetype)) {
            rightSource = InputSource.TARGET_OBJECT;
        }

        addQaComponent(workflowDescription, workflowContent, leftSource, rightSource, parameters, measures,
            relatedObject);
    }

    /**
     * Adds a QA component.
     *
     * The source for the left input is set to {@code leftSource}, the source
     * for the right input is set to {@code rightSource}. If these parameters
     * are null, the input are not connected.
     *
     * Adds measure ports for the provided {@code measures} to the workflow if
     * they are provided by the QA component and are not already present.
     *
     * @param workflowDescription
     *            workflow description of the migration component
     * @param workflowContent
     *            the actual workflow content of the component
     * @param leftSource
     *            the source of the left input or null
     * @param rightSource
     *            the source of the right input or null
     * @param parameters
     *            map with parameters of the component
     * @param measures
     *            measures of output ports to connect the component to
     * @param relatedObject
     *            the related object of measures to use if present
     */
    public void addQaComponent(final WorkflowDescription workflowDescription, final String workflowContent,
        final InputSource leftSource, final InputSource rightSource, final Map<String, String> parameters,
        final List<String> measures, RelatedObject relatedObject) {

        // Dataflow
        NestedWorkflow qa = new NestedWorkflow(createProcessorName(workflowDescription.getName()),
            workflowDescription.getDataflowId());
        workflow.addProcessor(qa);

        String dataflowContent = convertWorkflowToNested(workflowContent);
        workflow.addDataflow(new Dataflow(workflowDescription.getDataflowId(), dataflowContent));

        // Input ports
        List<Port> inputPorts = workflowDescription.getInputPorts();
        for (Port p : inputPorts) {
            if (ComponentConstants.VALUE_LEFT_OBJECT.equals(p.getValue())) {
                if (leftSource == InputSource.SOURCE_OBJECT) {
                    qa.addInputPort(new InputPort(p.getName(), 0));
                    workflow.addDatalink(new Datalink(workflow, SOURCE_PORT_NAME, qa, p.getName()));
                } else if (leftSource == InputSource.TARGET_OBJECT) {
                    qa.addInputPort(new InputPort(p.getName(), 0));
                    workflow.addDatalink(new Datalink(migration, migrationTargetPortName, qa, p.getName()));
                }
            } else if (ComponentConstants.VALUE_RIGHT_OBJECT.equals(p.getValue())) {
                if (rightSource == InputSource.SOURCE_OBJECT) {
                    qa.addInputPort(new InputPort(p.getName(), 0));
                    workflow.addDatalink(new Datalink(workflow, SOURCE_PORT_NAME, qa, p.getName()));
                } else if (rightSource == InputSource.TARGET_OBJECT) {
                    qa.addInputPort(new InputPort(p.getName(), 0));
                    workflow.addDatalink(new Datalink(migration, migrationTargetPortName, qa, p.getName()));
                }
            } else if (ComponentConstants.VALUE_PARAMETER.equals(p.getValue())) {
                qa.addInputPort(new InputPort(p.getName(), 0));
                TextConstant c = new TextConstant(p.getName(), parameters.get(p.getName()));
                workflow.addProcessor(c);
                workflow.addDatalink(new Datalink(c, "value", qa, p.getName()));
            }
        }

        // Output ports
        List<Port> outputPorts = workflowDescription.getOutputPorts();
        for (Port p : outputPorts) {
            if (measures.contains(p.getValue())) {
                if (p.getRelatedObject() == null || p.getRelatedObject().equals(relatedObject.toString())) {
                    String measurePortName = createMeasurePortName(p.getValue());
                    if (!workflow.hasSink(measurePortName)) {
                        addMeasurePort(p.getValue());
                    }
                    qa.addOutputPort(new OutputPort(p.getName()));
                    workflow.addDatalink(new Datalink(qa, p.getName(), workflow, measurePortName));
                }
            }
        }
    }

    /**
     * Adds a CC component connected to the workflow's target object.
     *
     * Adds measure ports for the provided {@code measures} to the workflow if
     * they are provided by the CC component and are not already present.
     *
     * @param workflowDescription
     *            workflow description of the migration component
     * @param workflowContent
     *            the actual workflow content of the component
     * @param parameters
     *            map with parameters of the component
     * @param measures
     *            measures of output ports to connect the component to
     *
     * @see {@link #addCcComponent(WorkflowDescription, String, Map, List, InputSource)}
     */
    public void addCcComponent(final WorkflowDescription workflowDescription, final String workflowContent,
        final Map<String, String> parameters, final List<String> measures) {
        addCcComponent(workflowDescription, workflowContent, parameters, measures, InputSource.TARGET_OBJECT);
    }

    /**
     * Adds a CC component connected to the provided {@code inputSource}.
     *
     * Adds measure ports for the provided {@code measures} to the workflow if
     * they are provided by the CC component and are not already present.
     *
     * @param workflowDescription
     *            workflow description of the migration component
     * @param workflowContent
     *            the actual workflow content of the component
     * @param parameters
     *            map with parameters of the component
     * @param measures
     *            measures of output ports to connect the component to
     * @param inputSource
     *            the input source of the CC component
     */
    public void addCcComponent(final WorkflowDescription workflowDescription, final String workflowContent,
        final Map<String, String> parameters, final List<String> measures, final InputSource inputSource) {
        // Dataflow
        NestedWorkflow cc = new NestedWorkflow(createProcessorName(workflowDescription.getName()),
            workflowDescription.getDataflowId());
        workflow.addProcessor(cc);

        String dataflowContent = convertWorkflowToNested(workflowContent);
        workflow.addDataflow(new Dataflow(workflowDescription.getDataflowId(), dataflowContent));

        // Input ports
        List<Port> inputPorts = workflowDescription.getInputPorts();
        for (Port p : inputPorts) {
            if (ComponentConstants.VALUE_SOURCE_OBJECT.equals(p.getValue())) {
                if (inputSource == InputSource.SOURCE_OBJECT && workflowDescription.handlesMimetype(sourceMimetype)) {
                    cc.addInputPort(new InputPort(p.getName(), 0));
                    workflow.addDatalink(new Datalink(workflow, SOURCE_PORT_NAME, cc, p.getName()));
                } else if (inputSource == InputSource.TARGET_OBJECT
                    && workflowDescription.handlesMimetype(targetMimetype)) {
                    cc.addInputPort(new InputPort(p.getName(), 0));
                    workflow.addDatalink(new Datalink(migration, migrationTargetPortName, cc, p.getName()));
                }
            } else if (ComponentConstants.VALUE_PARAMETER.equals(p.getValue())) {
                cc.addInputPort(new InputPort(p.getName(), 0));
                TextConstant c = new TextConstant(p.getName(), parameters.get(p.getName()));
                workflow.addProcessor(c);
                workflow.addDatalink(new Datalink(c, "value", cc, p.getName()));
            }
        }

        // Output ports
        List<Port> outputPorts = workflowDescription.getOutputPorts();
        for (Port p : outputPorts) {
            if (measures.contains(p.getValue())) {
                String measurePortName = createMeasurePortName(p.getValue());
                if (!workflow.hasSink(measurePortName)) {
                    addMeasurePort(p.getValue());
                }
                cc.addOutputPort(new OutputPort(p.getName()));
                workflow.addDatalink(new Datalink(cc, p.getName(), workflow, measurePortName));
            }
        }
    }

    /**
     * Generates the executable plan from this description and writes it to the
     * provided writer.
     *
     * @param writer
     *            the writer where the plan is written to
     * @throws IOException
     *             if an error occurred during write
     */
    public void generate(Writer writer) throws IOException {
        MustacheFactory mf = new DefaultMustacheFactory();
        Mustache mustache = mf.compile("data/t2flow/workflow.mustache");
        mustache.execute(writer, workflow).flush();
    }

    /**
     * Creates a valid port name from the provided measure URI.
     *
     * @param measure
     *            the measure to use
     * @return the port name or null if the measure format is not recognised
     */
    public static String createMeasurePortName(final String measure) {
        Matcher purlMatcher = PURL_DP_MEASURE_PATTERN.matcher(measure);
        if (purlMatcher.matches()) {
            return purlMatcher.group(1) + "_" + purlMatcher.group(2);
        }

        Matcher genericMatcher = GENERIC_MEASURE_PATTERN.matcher(measure);
        if (genericMatcher.matches()) {
            return genericMatcher.group(1).replaceAll("\\s", "_").replaceAll("\\W", "_");
        }
        return null;
    }

    /**
     * Creates a measure URL from the provided port name.
     *
     * @param portName
     *            the port name
     * @return the port name or null if the measure format is not recognised
     */
    public static String guessMeasureUrl(final String portName) {
        Matcher purlMatcher = PURL_DP_PORTNAME_PATTERN.matcher(portName);
        if (purlMatcher.matches()) {
            return "http://purl.org/DP/quality/" + purlMatcher.group(1) + "#" + purlMatcher.group(2);
        }
        return null;
    }

    /**
     * Creates a valid processor name from the provided name.
     *
     * @param name
     *            the name to use
     * @return a valid processor name
     */
    private String createProcessorName(final String name) {
        String baseName = name.replaceAll("\\s", "_").replaceAll("\\W", "");
        String processorName = baseName;
        int postfix = 1;
        while (containsProcessorName(processorName)) {
            postfix++;
            processorName = baseName + "_" + postfix;
        }
        return processorName;
    }

    /**
     * Checks if the workflow contains a processor with the provided
     * {@code name}.
     *
     * @param name
     *            the processor name
     * @return true if a processor with the name exists, false otherwise
     */
    private boolean containsProcessorName(final String name) {
        for (Processor p : workflow.getProcessors()) {
            if (p.getName().equals(name)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Converts the workflow content string to a nested dataflow string.
     *
     * Note that the returned string is not a valid workflow by itself anymore
     * but can be added as a dataflow element to an enclosing workflow.
     *
     * @param workflowContent
     *            the workflow to convert
     * @return the converted dataflow
     */
    private String convertWorkflowToNested(final String workflowContent) {
        return workflowContent.replaceAll("<\\?xml.*>", "").replaceAll("<workflow.+?>", "")
            .replaceAll("</workflow>", "").replaceAll("role=\"top\"", "role=\"nested\"");
    }

    /**
     * Checks if this generator has a migration.
     *
     * @return true if a migration was added, false otherwise
     */
    private boolean hasMigration() {
        return migration != null && migrationTargetPortName != null && !migrationTargetPortName.isEmpty();
    }
}
TOP

Related Classes of eu.scape_project.planning.services.taverna.generator.T2FlowExecutablePlanGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.