| package org.apache.taverna.scufl2.wfdesc; |
| |
| /* |
| * |
| * 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 static org.apache.taverna.scufl2.api.common.Scufl2Tools.NESTED_WORKFLOW; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import org.apache.jena.ontology.Individual; |
| import org.apache.jena.ontology.OntModel; |
| import org.apache.jena.rdf.model.ModelFactory; |
| import org.apache.jena.rdf.model.Resource; |
| import org.apache.jena.riot.Lang; |
| import org.apache.jena.riot.RDFDataMgr; |
| import org.apache.jena.riot.RiotException; |
| import org.apache.jena.vocabulary.OWL; |
| import org.apache.taverna.scufl2.api.activity.Activity; |
| import org.apache.taverna.scufl2.api.annotation.Annotation; |
| import org.apache.taverna.scufl2.api.common.Child; |
| import org.apache.taverna.scufl2.api.common.Named; |
| import org.apache.taverna.scufl2.api.common.Scufl2Tools; |
| import org.apache.taverna.scufl2.api.common.URITools; |
| import org.apache.taverna.scufl2.api.common.Visitor.VisitorWithPath; |
| 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.DataLink; |
| import org.apache.taverna.scufl2.api.core.Processor; |
| import org.apache.taverna.scufl2.api.core.Workflow; |
| import org.apache.taverna.scufl2.api.io.WriterException; |
| import org.apache.taverna.scufl2.api.port.InputPort; |
| import org.apache.taverna.scufl2.api.port.OutputPort; |
| import org.apache.taverna.scufl2.api.port.WorkflowPort; |
| import org.apache.taverna.scufl2.api.profiles.ProcessorBinding; |
| import org.apache.taverna.scufl2.api.profiles.ProcessorPortBinding; |
| import org.apache.taverna.scufl2.api.profiles.Profile; |
| import org.apache.taverna.scufl2.wfdesc.ontologies.Prov_o; |
| import org.apache.taverna.scufl2.wfdesc.ontologies.Roterms; |
| import org.apache.taverna.scufl2.wfdesc.ontologies.Wf4ever; |
| import org.apache.taverna.scufl2.wfdesc.ontologies.Wfdesc; |
| |
| import com.fasterxml.jackson.databind.JsonNode; |
| |
| public class WfdescSerialiser { |
| |
| private static Logger logger = Logger.getLogger(WfdescSerialiser.class.getCanonicalName()); |
| |
| public static URI REST = URI.create("http://ns.taverna.org.uk/2010/activity/rest"); |
| public static URI WSDL = URI.create("http://ns.taverna.org.uk/2010/activity/wsdl"); |
| public static URI SECURITY = WSDL.resolve("wsdl/security"); |
| public static URI OPERATION = WSDL.resolve("wsdl/operation"); |
| public static URI BEANSHELL = URI.create("http://ns.taverna.org.uk/2010/activity/beanshell"); |
| public static URI RSHELL = URI.create("http://ns.taverna.org.uk/2010/activity/rshell"); |
| public static URI TOOL = URI.create("http://ns.taverna.org.uk/2010/activity/tool"); |
| |
| private Scufl2Tools scufl2Tools = new Scufl2Tools(); |
| private URITools uriTools = new URITools(); |
| |
| public Scufl2Tools getScufl2Tools() { |
| return scufl2Tools; |
| } |
| |
| public URITools getUriTools() { |
| return uriTools; |
| } |
| |
| private String uriForBean(WorkflowBean bean) { |
| return uriTools.uriForBean(bean).toASCIIString(); |
| } |
| |
| protected OntModel save(final WorkflowBundle bundle) { |
| final OntModel model = ModelFactory.createOntologyModel(); |
| bundle.accept(new VisitorWithPath() { |
| Scufl2Tools scufl2Tools = new Scufl2Tools(); |
| |
| public boolean visit() { |
| WorkflowBean node = getCurrentNode(); |
| // System.out.println(node); |
| if (node instanceof WorkflowBundle) { |
| return true; |
| } |
| // @SuppressWarnings("rawtypes") |
| |
| if (node instanceof org.apache.taverna.scufl2.api.core.Workflow) { |
| entityForBean(node, Wfdesc.Workflow); |
| } else if (node instanceof Processor) { |
| Processor processor = (Processor) node; |
| Individual process = entityForBean(processor, Wfdesc.Process); |
| Individual wf = entityForBean(processor.getParent(), Wfdesc.Workflow); |
| wf.addProperty(Wfdesc.hasSubProcess, process); |
| } else if (node instanceof InputPort) { |
| WorkflowBean parent = ((Child) node).getParent(); |
| Individual input = entityForBean(node, Wfdesc.Input); |
| Individual process = entityForBean(parent, Wfdesc.Process); |
| process.addProperty(Wfdesc.hasInput, input); |
| |
| } else if (node instanceof OutputPort) { |
| WorkflowBean parent = ((Child) node).getParent(); |
| Individual output = entityForBean(node, Wfdesc.Output); |
| Individual process = entityForBean(parent, Wfdesc.Process); |
| process.addProperty(Wfdesc.hasOutput, output); |
| } else if (node instanceof DataLink) { |
| WorkflowBean parent = ((Child) node).getParent(); |
| DataLink link = (DataLink) node; |
| Individual dl = entityForBean(link, Wfdesc.DataLink); |
| |
| Individual source = entityForBean(link.getReceivesFrom(), Wfdesc.Output); |
| dl.addProperty(Wfdesc.hasSource, source); |
| |
| Individual sink = entityForBean(link.getSendsTo(), Wfdesc.Input); |
| dl.addProperty(Wfdesc.hasSink, sink); |
| Individual wf = entityForBean(parent, Wfdesc.Workflow); |
| wf.addProperty(Wfdesc.hasDataLink, dl); |
| } else if (node instanceof Profile) { |
| // So that we can get at the ProcessorBinding - buy only if |
| // it is the main Profile |
| return node == bundle.getMainProfile(); |
| } else if (node instanceof ProcessorBinding) { |
| ProcessorBinding b = (ProcessorBinding) node; |
| Activity a = b.getBoundActivity(); |
| Processor boundProcessor = b.getBoundProcessor(); |
| Individual process = entityForBean(boundProcessor, Wfdesc.Process); |
| |
| // Note: We don't describe the activity and processor |
| // binding in wfdesc. Instead we |
| // assign additional types and attributes to the parent |
| // processor |
| |
| try { |
| URI type = a.getType(); |
| Configuration c = scufl2Tools.configurationFor(a, b.getParent()); |
| JsonNode json = c.getJson(); |
| if (type.equals(BEANSHELL)) { |
| process.addRDFType(Wf4ever.BeanshellScript); |
| String s = json.get("script").asText(); |
| process.addProperty(Wf4ever.script, s); |
| JsonNode localDep = json.get("localDependency"); |
| if (localDep != null && localDep.isArray()) { |
| for (int i = 0; i < localDep.size(); i++) { |
| String depStr = localDep.get(i).asText(); |
| // FIXME: Better class for dependency? |
| Individual dep = model.createIndividual(OWL.Thing); |
| dep.addLabel(depStr, null); |
| dep.addComment("JAR dependency", "en"); |
| process.addProperty(Roterms.requiresSoftware, dep); |
| // Somehow this gets the whole thing to fall |
| // out of the graph! |
| // QName depQ = new |
| // QName("http://google.com/", ""+ |
| // UUID.randomUUID()); |
| // sesameManager.rename(dep, depQ); |
| |
| } |
| } |
| } |
| if (type.equals(RSHELL)) { |
| process.addRDFType(Wf4ever.RScript); |
| String s = json.get("script").asText(); |
| process.addProperty(Wf4ever.script, s); |
| } |
| if (type.equals(WSDL)) { |
| process.addRDFType(Wf4ever.SOAPService); |
| JsonNode operation = json.get("operation"); |
| URI wsdl = URI.create(operation.get("wsdl").asText()); |
| process.addProperty(Wf4ever.wsdlURI, wsdl.toASCIIString()); |
| process.addProperty(Wf4ever.wsdlOperationName, operation.get("name").asText()); |
| process.addProperty(Wf4ever.rootURI, wsdl.resolve("/").toASCIIString()); |
| } |
| if (type.equals(REST)) { |
| process.addRDFType(Wf4ever.RESTService); |
| // System.out.println(json); |
| JsonNode request = json.get("request"); |
| String absoluteURITemplate = request.get("absoluteURITemplate").asText(); |
| String uriTemplate = absoluteURITemplate.replace("{", ""); |
| uriTemplate = uriTemplate.replace("}", ""); |
| // TODO: Detect {} |
| try { |
| URI root = new URI(uriTemplate).resolve("/"); |
| process.addProperty(Wf4ever.rootURI, root.toASCIIString()); |
| } catch (URISyntaxException e) { |
| logger.warning("Potentially invalid URI template: " + absoluteURITemplate); |
| // Uncomment to temporarily break |
| // TestInvalidURITemplate: |
| // rest.getWfRootURI().add(URI.create("http://example.com/FRED")); |
| } |
| } |
| if (type.equals(TOOL)) { |
| process.addRDFType(Wf4ever.CommandLineTool); |
| JsonNode desc = json.get("toolDescription"); |
| // System.out.println(json); |
| JsonNode command = desc.get("command"); |
| if (command != null) { |
| process.addProperty(Wf4ever.command, command.asText()); |
| } |
| } |
| if (type.equals(NESTED_WORKFLOW)) { |
| Workflow nestedWf = scufl2Tools.nestedWorkflowForProcessor(boundProcessor, b.getParent()); |
| // The parent process is a specialization of the |
| // nested workflow |
| // (because the nested workflow could exist as |
| // several processors) |
| specializationOf(boundProcessor, nestedWf); |
| process.addRDFType(Wfdesc.Workflow); |
| |
| // Just like the Processor specializes the nested |
| // workflow, the |
| // ProcessorPorts specialize the WorkflowPort |
| for (ProcessorPortBinding portBinding : b.getInputPortBindings()) { |
| // Map from activity port (not in wfdesc) to |
| // WorkflowPort |
| WorkflowPort wfPort = nestedWf.getInputPorts() |
| .getByName(portBinding.getBoundActivityPort().getName()); |
| if (wfPort == null) { |
| continue; |
| } |
| specializationOf(portBinding.getBoundProcessorPort(), wfPort); |
| } |
| for (ProcessorPortBinding portBinding : b.getOutputPortBindings()) { |
| WorkflowPort wfPort = nestedWf.getOutputPorts() |
| .getByName(portBinding.getBoundActivityPort().getName()); |
| if (wfPort == null) { |
| continue; |
| } |
| specializationOf(portBinding.getBoundProcessorPort(), wfPort); |
| } |
| } |
| } catch (IndexOutOfBoundsException ex) { |
| } |
| return false; |
| } else { |
| // System.out.println("--NO!"); |
| return false; |
| } |
| for (Annotation ann : scufl2Tools.annotationsFor(node, bundle)) { |
| String annotationBody = ann.getBody().toASCIIString(); |
| String baseURI = bundle.getGlobalBaseURI().resolve(ann.getBody()).toASCIIString(); |
| InputStream annotationStream; |
| try { |
| annotationStream = bundle.getResources().getResourceAsInputStream(annotationBody); |
| |
| try { |
| // FIXME: Don't just assume Lang.TURTLE |
| RDFDataMgr.read(model, annotationStream, baseURI, Lang.TURTLE); |
| } catch (RiotException e) { |
| logger.log(Level.WARNING, "Can't parse RDF Turtle from " + annotationBody, e); |
| } finally { |
| annotationStream.close(); |
| } |
| } catch (IOException e) { |
| logger.log(Level.WARNING, "Can't read " + annotationBody, e); |
| } |
| } |
| if (node instanceof Named) { |
| Named named = (Named) node; |
| entityForBean(node, OWL.Thing).addLabel(named.getName(), null); |
| } |
| return true; |
| } |
| |
| private void specializationOf(WorkflowBean special, WorkflowBean general) { |
| Individual specialEnt = entityForBean(special, Prov_o.Entity); |
| Individual generalEnt = entityForBean(general, Prov_o.Entity); |
| specialEnt.addProperty(Prov_o.specializationOf, generalEnt); |
| } |
| |
| private Individual entityForBean(WorkflowBean bean, Resource thing) { |
| return model.createIndividual(uriForBean(bean), thing); |
| } |
| |
| // @Override |
| // public boolean visitEnter(WorkflowBean node) { |
| // if (node instanceof Processor |
| // || node instanceof org.apache.taverna.scufl2.api.core.Workflow |
| // || node instanceof Port || node instanceof DataLink) { |
| // visit(node); |
| // return true; |
| // } |
| // // The other node types (e.g. dispatch stack, configuration) are |
| // // not (directly) represented in wfdesc |
| //// System.out.println("Skipping " + node); |
| // return false; |
| // }; |
| }); |
| return model; |
| } |
| |
| public void save(WorkflowBundle wfBundle, OutputStream output) throws WriterException { |
| OntModel model; |
| |
| final URI baseURI; |
| if (wfBundle.getMainWorkflow() != null) { |
| Workflow mainWorkflow = wfBundle.getMainWorkflow(); |
| baseURI = uriTools.uriForBean(mainWorkflow); |
| model = save(wfBundle); |
| } else { |
| throw new WriterException("wfdesc format requires a main workflow"); |
| } |
| |
| model.setNsPrefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#"); |
| model.setNsPrefix("xsd", "http://www.w3.org/2001/XMLSchema#"); |
| model.setNsPrefix("owl", "http://www.w3.org/2002/07/owl#"); |
| model.setNsPrefix("prov", "http://www.w3.org/ns/prov#"); |
| model.setNsPrefix("wfdesc", "http://purl.org/wf4ever/wfdesc#"); |
| model.setNsPrefix("wf4ever", "http://purl.org/wf4ever/wf4ever#"); |
| model.setNsPrefix("roterms", "http://purl.org/wf4ever/roterms#"); |
| model.setNsPrefix("dc", "http://purl.org/dc/elements/1.1/"); |
| model.setNsPrefix("dcterms", "http://purl.org/dc/terms/"); |
| model.setNsPrefix("comp", "http://purl.org/DP/components#"); |
| model.setNsPrefix("dep", "http://scape.keep.pt/vocab/dependencies#"); |
| model.setNsPrefix("biocat", "http://biocatalogue.org/attribute/"); |
| |
| model.setNsPrefix("", "#"); |
| |
| try { |
| model.write(output, Lang.TURTLE.getName(), baseURI.toString()); |
| } catch (RiotException e) { |
| throw new WriterException("Can't write to output", e); |
| } |
| |
| } |
| |
| public void setScufl2Tools(Scufl2Tools scufl2Tools) { |
| this.scufl2Tools = scufl2Tools; |
| } |
| |
| public void setUriTools(URITools uriTools) { |
| this.uriTools = uriTools; |
| } |
| |
| } |