| package org.apache.taverna.examples; |
| |
| /* |
| * 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.io.File; |
| import java.io.IOException; |
| import java.net.URI; |
| |
| import org.apache.taverna.scufl2.api.activity.Activity; |
| import org.apache.taverna.scufl2.api.common.Scufl2Tools; |
| import org.apache.taverna.scufl2.api.configurations.Configuration; |
| import org.apache.taverna.scufl2.api.container.WorkflowBundle; |
| 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.io.ReaderException; |
| import org.apache.taverna.scufl2.api.io.WorkflowBundleIO; |
| import org.apache.taverna.scufl2.api.io.WriterException; |
| import org.apache.taverna.scufl2.api.iterationstrategy.DotProduct; |
| import org.apache.taverna.scufl2.api.iterationstrategy.PortNode; |
| import org.apache.taverna.scufl2.api.port.InputActivityPort; |
| 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.OutputProcessorPort; |
| import org.apache.taverna.scufl2.api.port.OutputWorkflowPort; |
| 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.Profile; |
| |
| public class WorkflowMaker { |
| private static Scufl2Tools scufl2Tools = new Scufl2Tools(); |
| private static WorkflowBundleIO bundleIO = new WorkflowBundleIO(); |
| |
| public static void main(String[] args) throws Exception { |
| new WorkflowMaker().makeWorkflowBundle(); |
| } |
| |
| protected WorkflowBundle bundle; |
| protected Workflow workflow; |
| protected Processor p; |
| protected InputProcessorPort pIn; |
| protected OutputProcessorPort pOut; |
| protected Profile profile; |
| protected Activity myBeanshell; |
| protected File file; |
| |
| |
| public void makeWorkflowBundle() throws IOException, WriterException, |
| ReaderException { |
| |
| /** Top-level object is a Workflow Bundle */ |
| bundle = new WorkflowBundle(); |
| |
| /** Generate the workflow structure **/ |
| makeWorkflow(); |
| |
| /** Specify the implementations **/ |
| makeProfile(); |
| |
| /** |
| * Before storing the workflow bundle, we'll make sure that everything |
| * we made has a parent included (so that for instance a configuration |
| * is stored together with its parent profile). The |
| * scufl2Tools.setParents method will traverse the WorkflowBundle from |
| * the top and fill in any blank parents. |
| */ |
| scufl2Tools.setParents(bundle); |
| |
| /** Write bundle to StdOut and a new file */ |
| writeBundleToFile(); |
| |
| } |
| |
| |
| private void makeWorkflow() { |
| workflow = new Workflow(); |
| /** Workflow names must be unique within the WorkflowBundle */ |
| workflow.setName("Echotest"); |
| |
| bundle.setMainWorkflow(workflow); |
| /** |
| * Additional (typically nested) workflows can be added: |
| * |
| * <pre> |
| * bundle.getWorkflows().add(workflow2) |
| * </pre> |
| * |
| * but the above is implied by setMainWorkflow() |
| */ |
| |
| /** Creating and adding a workflow port */ |
| InputWorkflowPort in1 = new InputWorkflowPort(); |
| in1.setName("in1"); |
| in1.setDepth(0); |
| /** where does this input port belong? */ |
| in1.setParent(workflow); |
| /** |
| * implies: |
| * |
| * <pre> |
| * workflow.getInputPorts().add(in1); |
| * </pre> |
| */ |
| |
| /** |
| * If input should be a list instead of single value: |
| * |
| * <pre> |
| * in1.setDepth(1); |
| * </pre> |
| */ |
| |
| /** Output, this time using the shorthand constructors */ |
| OutputWorkflowPort out1 = new OutputWorkflowPort(workflow, "out1"); |
| |
| /** |
| * A processor is a unit which performs some work in a workflow. The |
| * name must be unique within the parent workflow. |
| * |
| */ |
| p = new Processor(workflow, "p"); |
| |
| /** |
| * Same as: |
| * |
| * <pre> |
| * Processor p = new Processor(); |
| * p.setName("p"); |
| * p.setParent(workflow); |
| * workflow.getProcessors().add(p); |
| * </pre> |
| */ |
| |
| /** |
| * Processors typically have inputs and outputs which are connected |
| * within the workflow |
| */ |
| pIn = new InputProcessorPort(p, "pIn"); |
| pIn.setDepth(0); |
| pOut = new OutputProcessorPort(p, "pOut"); |
| pOut.setDepth(0); |
| pOut.setGranularDepth(0); |
| /** |
| * .. any additional ports must have a unique name within the input or |
| * output ports of that processor. |
| */ |
| |
| /** |
| * Defining a data link from the workflow input port 'in1' to the |
| * processor input port 'pIn' - this means that data will flow from |
| * 'in1' to 'pIn'. |
| */ |
| DataLink link = new DataLink(); |
| link.setReceivesFrom(in1); |
| link.setSendsTo(pIn); |
| /** |
| * The ports must be either processor or workflow ports, and both of the |
| * same workflow as the datalink is added to: |
| */ |
| workflow.getDataLinks().add(link); |
| |
| /** |
| * Or more compact style: pOut -> out1 .. connecting processor output |
| * port 'pOut' to the workflow output port 'out1' |
| */ |
| new DataLink(workflow, pOut, out1); |
| /** |
| * the constructor will perform for us: |
| * |
| * <pre> |
| * setParent(workflow) |
| * workflow.getDataLinks().add(workflow) |
| * </pre> |
| */ |
| |
| /** |
| * Note: As datalinks are unique based on the connection, they don't |
| * have names |
| */ |
| |
| /** |
| * Not covered by this example: |
| * |
| * Dispatch stack: |
| * |
| * <pre> |
| * DispatchStackLayer dispatchStackLayer = new DispatchStackLayer(); |
| * dispatchStackLayer |
| * .setConfigurableType(URI |
| * .create("http://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Retry")); |
| * p.getDispatchStack().add(dispatchStackLayer); |
| * Configuration retryConfig = new Configuration(); |
| * retryConfig.setConfigures(dispatchStackLayer); |
| * // .. |
| * </pre> |
| */ |
| |
| /* |
| * Iteration strategies: |
| */ |
| DotProduct dot = new DotProduct(); |
| PortNode e = new PortNode(dot, pIn); |
| e.setDesiredDepth(0); |
| p.getIterationStrategyStack().add(dot); |
| |
| } |
| |
| |
| private void makeProfile() { |
| profile = new Profile("default"); |
| |
| /** |
| * One profile can be suggest as the 'main' profile - but alternative |
| * execution profiles can also be added, for instance to provide |
| * Grid-based activities rather than Web Service activities - each of |
| * them must have a unique name within the profiles of the workflow |
| * bundle. |
| */ |
| bundle.setMainProfile(profile); |
| |
| /** |
| * Additional profiles can be added with: |
| * |
| * <pre> |
| * bundle.getProfiles().add(profile2); |
| * </pre> |
| */ |
| |
| myBeanshell = new Activity("myBeanshell"); |
| |
| /** |
| * Activities are of different types, identified by an URI. A workflow |
| * engine will typically expose which activity types it supports. |
| * <p> |
| * The default types of Taverna have the prefix |
| * http://ns.taverna.org.uk/2010/activity/, but other plugins will have |
| * different URI bases. |
| */ |
| URI BEANSHELL = URI |
| .create("http://ns.taverna.org.uk/2010/activity/beanshell"); |
| myBeanshell.setType(BEANSHELL); |
| |
| |
| /** |
| * Activities are activated within a particular profile (Therefore |
| * execution of a profile requires the engine to support all types of |
| * all activities of the profile) |
| */ |
| profile.getActivities().add(myBeanshell); |
| |
| makeConfiguration(); |
| |
| /** |
| * A Processor Binding connects a Processor ('p') with an Activity |
| * 'myBeanshell'. This means that execution of p will use the activity. |
| */ |
| ProcessorBinding binding = new ProcessorBinding(); |
| binding.setBoundProcessor(p); |
| binding.setBoundActivity(myBeanshell); |
| |
| /** |
| * It is possible, but not common, for multiple processor bindings to |
| * reuse the same activity. On execution, the workflow engine might or |
| * might not instantiate this as the same activity implementation. |
| */ |
| |
| /** And add binding to the profile */ |
| binding.setParent(profile); |
| /** |
| * alternatively: |
| * |
| * <pre> |
| * profile.getProcessorBindings().add(binding) |
| * </pre> |
| */ |
| |
| /** |
| * It is possible to bind more than one activity for the same processor, |
| * in which case they will be used as alternate services on failure. (As |
| * the default Dispatch Stack contains the Failover layer). In this |
| * case, the processor bindings should specify the 'activity position', |
| * which determines the ordering of activities within a processor: |
| * |
| * <pre> |
| * binding.setActivityPosition(15); |
| * </pre> |
| */ |
| |
| /** |
| * Activities have input and output ports as well, normally these match |
| * one-to-one with the bound processor's port names and depth. |
| */ |
| InputActivityPort aIn1 = new InputActivityPort(myBeanshell, "in1"); |
| aIn1.setDepth(0); |
| myBeanshell.getInputPorts().add(aIn1); |
| OutputActivityPort aOut1 = new OutputActivityPort(myBeanshell, "out1"); |
| aOut1.setDepth(0); |
| aOut1.setGranularDepth(0); |
| myBeanshell.getOutputPorts().add(aOut1); |
| |
| |
| |
| /** |
| * But in case the activities don't match up (such as when multiple |
| * activities are bound to the same processor, or as in this example |
| * where the port matches the script), a port mapping must be specified |
| * in the processor binding: |
| */ |
| binding.getInputPortBindings().add( |
| new ProcessorInputPortBinding(binding, pIn, aIn1)); |
| new ProcessorOutputPortBinding(binding, aOut1, pOut); |
| |
| /** |
| * It is not required to bind any processor input port, but many |
| * activities expect some or all their inputs bound. It is not required |
| * to bind all activity output ports, but all processor output ports |
| * must be bound for each processor binding. |
| */ |
| |
| /** If the port names match up, the above can all be done in one go with */ |
| //scufl2Tools.bindActivityToProcessorByMatchingPorts(myBeanshell, p); |
| } |
| |
| |
| |
| private void makeConfiguration() { |
| URI BEANSHELL = URI |
| .create("http://ns.taverna.org.uk/2010/activity/beanshell"); |
| /** |
| * Most activities also require a configuration in order to run. The |
| * name of the configuration is not important, but must be unique within |
| * the configurations of a profile. The default constructor |
| * Configuration() generates a UUID-based name as a fallback. |
| */ |
| Configuration beanshellConfig = new Configuration("beanshellConf"); |
| /** |
| * The activity we configure. (DispatchStackLayer can also be |
| * configured) |
| */ |
| beanshellConfig.setConfigures(myBeanshell); |
| /** |
| * A configuration is of a specified type (specified as an URI), which |
| * is typically related to (but different from) the activity type - but |
| * might in some cases be shared amongst several activity types. |
| */ |
| beanshellConfig.setType(BEANSHELL.resolve("#Config")); |
| |
| /** |
| * Configurations are normally shared in the same profile as the |
| * activity they configure (the parent) - but in some cases might also |
| * be added by other profiles in order to reuse a configuration across |
| * profiles. (Note: A profile is *stored* within its parent profile). |
| */ |
| beanshellConfig.setParent(profile); |
| profile.getConfigurations().add(beanshellConfig); |
| |
| /** |
| * Depending on the configuration type specified above, certain |
| * *properties* should be specified, and other properties might be |
| * optional. In this case, only "script" is |
| * specified, as a string value. (more complex properties can be |
| * specified using Jackson JSON methods of the ObjectNode) |
| */ |
| beanshellConfig.getJsonAsObjectNode().put("script", "out1 = in1"); |
| /** |
| * Note that property names are specified as URIs, which are often |
| * related to the URI of the configuration type - but might be reused |
| * across several configuration types. |
| */ |
| } |
| |
| |
| private void writeBundleToFile() throws IOException, WriterException, |
| ReaderException { |
| file = File.createTempFile("test", ".wfbundle"); |
| |
| /** |
| * Bundle IO |
| */ |
| bundleIO.writeBundle(bundle, file, |
| "application/vnd.taverna.scufl2.workflow-bundle"); |
| System.out.println("Written to " + file + "\n"); |
| |
| // Read it back in |
| WorkflowBundle secondBundle = bundleIO.readBundle(file, |
| "application/vnd.taverna.scufl2.workflow-bundle"); |
| |
| // Write in a debug text format |
| bundleIO.writeBundle(secondBundle, System.out, |
| "text/vnd.taverna.scufl2.structure"); |
| } |
| } |