| /* |
| * 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. |
| */ |
| package org.apache.taverna.prov; |
| |
| import static org.apache.taverna.scufl2.translator.t2flow.T2FlowParser.ravenURI; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.lang.reflect.InvocationTargetException; |
| import java.math.BigDecimal; |
| import java.net.URI; |
| import java.nio.charset.Charset; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.sql.Timestamp; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.GregorianCalendar; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.UUID; |
| |
| import javax.xml.datatype.DatatypeConfigurationException; |
| import javax.xml.datatype.DatatypeFactory; |
| import javax.xml.datatype.XMLGregorianCalendar; |
| |
| import net.sf.taverna.raven.appconfig.ApplicationConfig; |
| import org.apache.taverna.provenance.api.ProvenanceAccess; |
| import org.apache.taverna.provenance.lineageservice.URIGenerator; |
| import org.apache.taverna.provenance.lineageservice.utils.DataflowInvocation; |
| import org.apache.taverna.provenance.lineageservice.utils.Port; |
| import org.apache.taverna.provenance.lineageservice.utils.ProcessorEnactment; |
| import org.apache.taverna.provenance.lineageservice.utils.ProvenanceProcessor; |
| import org.apache.taverna.provenance.lineageservice.utils.WorkflowRun; |
| import org.apache.taverna.reference.ErrorDocument; |
| import org.apache.taverna.reference.IdentifiedList; |
| import org.apache.taverna.reference.StackTraceElementBean; |
| import org.apache.taverna.reference.T2Reference; |
| import org.apache.taverna.reference.T2ReferenceType; |
| import org.apache.taverna.spi.SPIRegistry; |
| |
| import org.apache.commons.beanutils.BeanUtils; |
| import org.apache.commons.beanutils.PropertyUtils; |
| import org.apache.jena.riot.RDFDataMgr; |
| import org.apache.jena.riot.RDFFormat; |
| import org.apache.jena.riot.WriterGraphRIOT; |
| import org.apache.jena.riot.system.RiotLib; |
| import org.apache.log4j.Logger; |
| import org.purl.wf4ever.provtaverna.owl.TavernaProvModel; |
| import org.apache.taverna.robundle.Bundle; |
| import org.apache.taverna.robundle.manifest.Agent; |
| import org.apache.taverna.robundle.manifest.Manifest; |
| import org.apache.taverna.robundle.manifest.PathAnnotation; |
| import org.apache.taverna.robundle.manifest.PathMetadata; |
| |
| import org.apache.taverna.databundle.DataBundles; |
| import org.apache.taverna.scufl2.api.common.URITools; |
| import org.apache.taverna.scufl2.api.container.WorkflowBundle; |
| import org.apache.taverna.scufl2.api.io.ReaderException; |
| import org.apache.taverna.scufl2.api.io.WorkflowBundleIO; |
| import org.apache.taverna.scufl2.api.io.WorkflowBundleReader; |
| import org.apache.taverna.scufl2.api.io.WorkflowBundleWriter; |
| import org.apache.taverna.scufl2.translator.t2flow.T2FlowReader; |
| |
| import org.apache.jena.datatypes.xsd.XSDDatatype; |
| import org.apache.jena.ontology.Individual; |
| import org.apache.jena.ontology.OntModel; |
| import org.apache.jena.rdf.model.Literal; |
| import org.apache.jena.rdf.model.Model; |
| import org.apache.jena.rdf.model.ModelFactory; |
| import org.apache.jena.sparql.util.Context; |
| import org.apache.jena.sparql.vocabulary.FOAF; |
| |
| public class W3ProvenanceExport { |
| |
| private static URITools uriTools = new URITools(); |
| |
| private static final URI osgiURI = URI.create("http://ns.taverna.org.uk/2013/osgibundle/"); |
| private static final String TEXT = "text/"; |
| private static final String WORKFLOW_BUNDLE = "application/vnd.taverna.scufl2.workflow-bundle"; |
| // TODO: Avoid this Taverna 2 dependency |
| private static SPIRegistry<WorkflowBundleReader> readerSpi = new SPIRegistry<>( |
| WorkflowBundleReader.class); |
| private static SPIRegistry<WorkflowBundleWriter> writerSpi = new SPIRegistry<>( |
| WorkflowBundleWriter.class); |
| |
| private static final String EN = "en"; |
| |
| private static final int EMBEDDED_MAX_FILESIZE = 1024; |
| |
| private static final Charset UTF8 = Charset.forName("UTF-8"); |
| |
| private static ApplicationConfig applicationConfig = ApplicationConfig |
| .getInstance(); |
| |
| private static Logger logger = Logger.getLogger(W3ProvenanceExport.class); |
| |
| protected Map<T2Reference, Path> seenReferences = new HashMap<>(); |
| |
| private static final int NANOSCALE = 9; |
| |
| private ProvenanceAccess provenanceAccess; |
| |
| private DatatypeFactory datatypeFactory; |
| |
| private ProvenanceURIGenerator uriGenerator = new ProvenanceURIGenerator(); |
| |
| private String workflowRunId; |
| |
| private Map<Path, T2Reference> fileToT2Reference = Collections.emptyMap(); |
| |
| private Saver saver; |
| |
| private Map<URI, Individual> describedEntities = new HashMap<URI, Individual>(); |
| |
| private TavernaProvModel provModel = new TavernaProvModel(); |
| |
| public Path getBaseFolder() { |
| return bundle.getRoot(); |
| } |
| |
| public Map<Path, T2Reference> getFileToT2Reference() { |
| return fileToT2Reference; |
| } |
| |
| // protected <I> void repopulateRegistry(ServiceRegistry<?, I> registry, |
| // Class<I> spi) { |
| // ClassLoader cl = classLoaderForServiceLoader(spi); |
| // logger.info("Selected classloader " + cl + " for registry of " + spi); |
| // for (I service : ServiceLoader.load(spi, cl)) { |
| // registry.add(service); |
| // } |
| // } |
| |
| // private ClassLoader classLoaderForServiceLoader(Class<?> mustHave) { |
| // List<ClassLoader> possibles = Arrays.asList(Thread.currentThread() |
| // .getContextClassLoader(), getClass().getClassLoader(), mustHave |
| // .getClassLoader()); |
| // |
| // for (ClassLoader cl : possibles) { |
| // if (cl == null) { |
| // continue; |
| // } |
| // try { |
| // if (cl.loadClass(mustHave.getCanonicalName()) == mustHave) { |
| // return cl; |
| // } |
| // } catch (ClassNotFoundException e) { |
| // } |
| // } |
| // // Final fall-back, the old.. |
| // return ClassLoader.getSystemClassLoader(); |
| // } |
| |
| public W3ProvenanceExport(ProvenanceAccess provenanceAccess, |
| String workflowRunId, Saver saver) { |
| this.saver = saver; |
| this.setWorkflowRunId(workflowRunId); |
| this.setProvenanceAccess(provenanceAccess); |
| |
| try { |
| datatypeFactory = DatatypeFactory.newInstance(); |
| } catch (DatatypeConfigurationException e) { |
| throw new IllegalStateException( |
| "Can't find a DatatypeFactory implementation", e); |
| } |
| prepareScufl2(); |
| |
| } |
| |
| protected void prepareScufl2() { |
| Thread.currentThread().setContextClassLoader( |
| getClass().getClassLoader()); |
| wfBundleIO = new WorkflowBundleIO(); |
| DataBundles.setWfBundleIO(wfBundleIO); |
| wfBundleIO.setReaders(readerSpi.getInstances()); |
| wfBundleIO.setWriters(writerSpi.getInstances()); |
| } |
| |
| private final class ProvenanceURIGenerator extends URIGenerator { |
| |
| // Make URIs match with Scufl2 |
| @Override |
| public String makeWorkflowURI(String workflowID) { |
| return makeWorkflowBundleURI(workflowRunId) + "workflow/" |
| + provenanceAccess.getWorkflowNameByWorkflowID(workflowID) |
| + "/"; |
| } |
| |
| public String makeWorkflowBundleURI(String workflowRunId) { |
| |
| return "http://ns.taverna.org.uk/2010/workflowBundle/" |
| + provenanceAccess.getTopLevelWorkflowID(workflowRunId) |
| + "/"; |
| } |
| |
| public String makePortURI(String wfId, String pName, String vName, |
| boolean inputPort) { |
| String base; |
| if (pName == null) { |
| base = makeWorkflowURI(wfId); |
| } else { |
| base = makeProcessorURI(pName, wfId); |
| } |
| return base + (inputPort ? "in/" : "out/") + escape(vName); |
| } |
| |
| // public String makeDataflowInvocationURI(String workflowRunId, |
| // String dataflowInvocationId) { |
| // return makeWFInstanceURI(workflowRunId) + "workflow/" |
| // + dataflowInvocationId + "/"; |
| // } |
| |
| public String makeProcessExecution(String workflowRunId, |
| String processEnactmentId) { |
| return makeWFInstanceURI(workflowRunId) + "process/" |
| + processEnactmentId + "/"; |
| } |
| } |
| |
| enum Direction { |
| INPUTS("in"), OUTPUTS("out"); |
| private final String path; |
| |
| Direction(String path) { |
| this.path = path; |
| |
| } |
| |
| public String getPath() { |
| return path; |
| } |
| } |
| |
| public void exportAsW3Prov() throws IOException { |
| |
| Path provFile = DataBundles.getWorkflowRunProvenance(bundle); |
| |
| // TODO: Make this thread safe using contexts? |
| |
| GregorianCalendar startedProvExportAt = new GregorianCalendar(); |
| |
| runURI = URI.create(uriGenerator.makeWFInstanceURI(getWorkflowRunId())); |
| |
| URI provFileUri = toURI(provFile); |
| Individual bundle = provModel.createBundle(provFileUri); |
| |
| // Mini-provenance about this provenance trace. Unkown URI for |
| // agent/activity |
| |
| Individual storeProvenance = provModel.createActivity(provFileUri |
| .resolve("#taverna-prov-export")); |
| storeProvenance.setLabel( |
| "taverna-prov export of workflow run provenance", EN); |
| |
| provModel.setStartedAtTime(storeProvenance, startedProvExportAt); |
| |
| // The agent is an execution of the Taverna software (e.g. also an |
| // Activity) |
| Individual tavernaAgent = provModel.createTavernaEngine(provFileUri |
| .resolve("#taverna-engine")); |
| |
| Individual plan = provModel |
| .createPlan(getTavernaVersion()); |
| plan.setLabel(applicationConfig.getTitle(), EN); |
| |
| provModel.setWasAssociatedWith(storeProvenance, tavernaAgent, plan); |
| provModel.setWasGeneratedBy(bundle, storeProvenance); |
| |
| Individual wfProcess = provModel.createWorkflowRun(runURI); |
| |
| bundle.setPropertyValue(FOAF.primaryTopic, wfProcess); |
| |
| DataflowInvocation dataflowInvocation = provenanceAccess |
| .getDataflowInvocation(getWorkflowRunId()); |
| |
| // TODO: Should we go through all of getDataflowInvocations() in order |
| // to find |
| // the plans etc. for the nested workflow executions and also cover |
| // empty |
| // nested workflow runs? |
| |
| String workflowName = provenanceAccess |
| .getWorkflowNameByWorkflowID(dataflowInvocation.getWorkflowId()); |
| label(wfProcess, "Workflow run of " + workflowName); |
| |
| provModel.setWasInformedBy(storeProvenance, wfProcess); |
| String wfUri = uriGenerator.makeWorkflowURI(dataflowInvocation |
| .getWorkflowId()); |
| Individual wfPlan = provModel.createWorkflow(URI.create(wfUri)); |
| provModel.setWasEnactedBy(wfProcess, tavernaAgent, wfPlan); |
| provModel.setDescribedByWorkflow(wfProcess, wfPlan); |
| |
| provModel.setStartedAtTime(wfProcess, |
| timestampToLiteral(dataflowInvocation.getInvocationStarted())); |
| provModel.setEndedAtTime(wfProcess, |
| timestampToLiteral(dataflowInvocation.getInvocationEnded())); |
| |
| // Workflow inputs and outputs |
| storeEntitities(dataflowInvocation.getInputsDataBindingId(), wfProcess, |
| Direction.INPUTS, true); |
| // FIXME: These entities come out as "generated" by multiple processes |
| storeEntitities(dataflowInvocation.getOutputsDataBindingId(), |
| wfProcess, Direction.OUTPUTS, true); |
| List<ProcessorEnactment> processorEnactments = provenanceAccess |
| .getProcessorEnactments(getWorkflowRunId()); |
| // This will also include processor enactments in nested workflows |
| for (ProcessorEnactment pe : processorEnactments) { |
| String parentId = pe.getParentProcessorEnactmentId(); |
| URI parentURI; |
| if (parentId == null) { |
| // Top-level workflow |
| parentURI = runURI; |
| } else { |
| // inside nested wf - this will be parent processenactment |
| parentURI = URI.create(uriGenerator.makeProcessExecution( |
| pe.getWorkflowRunId(), |
| pe.getParentProcessorEnactmentId())); |
| |
| // TODO: Find plan for nested workflow! |
| // String wfUri = uriGenerator.makeWorkflowURI(nestedWfId); |
| // Individual wfPlan = |
| // provModel.createWorkflow(URI.create(wfUri)); |
| // provModel.setDescribedByWorkflow(wfProcess, wfPlan); |
| // provModel.setWasEnactedBy(wfProcess, tavernaAgent, wfPlan); |
| } |
| |
| URI processURI = URI.create(uriGenerator.makeProcessExecution( |
| pe.getWorkflowRunId(), pe.getProcessEnactmentId())); |
| |
| Individual process = provModel.createProcessRun(processURI); |
| Individual parentProcess = provModel.createWorkflowRun(parentURI); |
| provModel.setWasPartOfWorkflowRun(process, parentProcess); |
| |
| provModel.setStartedAtTime(process, |
| timestampToLiteral(pe.getEnactmentStarted())); |
| provModel.setEndedAtTime(process, |
| timestampToLiteral(pe.getEnactmentEnded())); |
| |
| ProvenanceProcessor provenanceProcessor = provenanceAccess |
| .getProvenanceProcessor(pe.getProcessorId()); |
| |
| URI processorURI = URI.create(uriGenerator.makeProcessorURI( |
| provenanceProcessor.getProcessorName(), |
| provenanceProcessor.getWorkflowId())); |
| |
| label(process, |
| "Processor execution " |
| + provenanceProcessor.getProcessorName()); |
| // The facade identifier is a bit too techie! |
| // + " (" |
| // + pe.getProcessIdentifier() + ")"); |
| Individual procPlan = provModel.createProcess(processorURI); |
| label(procPlan, |
| "Processor " + provenanceProcessor.getProcessorName()); |
| provModel.setWasEnactedBy(process, tavernaAgent, procPlan); |
| provModel.setDescribedByProcess(process, procPlan); |
| |
| URI parentWfUri = URI.create(uriGenerator |
| .makeWorkflowURI(provenanceProcessor.getWorkflowId())); |
| |
| Individual parentWf = provModel.createWorkflow(parentWfUri); |
| provModel.addSubProcess(parentWf, procPlan); |
| |
| // TODO: How to link together iterations on a single processor and |
| // the collections |
| // they are iterating over and creating? |
| // Need 'virtual' ProcessExecution for iteration? |
| |
| // TODO: Activity/service details from definition? |
| |
| // Inputs and outputs |
| storeEntitities(pe.getInitialInputsDataBindingId(), process, |
| Direction.INPUTS, false); |
| storeEntitities(pe.getFinalOutputsDataBindingId(), process, |
| Direction.OUTPUTS, false); |
| } |
| |
| storeFileReferences(); |
| |
| provModel.setEndedAtTime(storeProvenance, new GregorianCalendar()); |
| |
| // provModel.model.write(outStream, "TURTLE", |
| // provFileUri.toASCIIString()); |
| |
| OntModel model = provModel.model; |
| try (OutputStream outStream = Files.newOutputStream(provFile)) { |
| WriterGraphRIOT writer = RDFDataMgr |
| .createGraphWriter(RDFFormat.TURTLE_BLOCKS); |
| writer.write(outStream, model.getBaseModel().getGraph(), |
| RiotLib.prefixMap(model.getGraph()), |
| provFileUri.toString(), new Context()); |
| } finally { |
| // Avoid registering the RIOT readers/writers from ARQ, as that |
| // won't |
| // work within Raven or OSGi |
| provModel.resetJena(); |
| logger.warn("Reset Jena readers and writers"); |
| } |
| |
| byte[] dataflow = getDataflow(dataflowInvocation); |
| try { |
| WorkflowBundle wfBundle = wfBundleIO.readBundle( |
| new ByteArrayInputStream(dataflow), |
| T2FlowReader.APPLICATION_VND_TAVERNA_T2FLOW_XML); |
| writeBundle(wfBundle); |
| } catch (ReaderException e) { |
| logger.warn("Could not write bundle", e); |
| } |
| |
| } |
| |
| private URI getTavernaVersion() { |
| String versionName = applicationConfig.getName(); |
| URI tavernaVersion = URI |
| .create("http://ns.taverna.org.uk/2011/software/" + versionName); |
| return tavernaVersion; |
| } |
| |
| private byte[] getDataflow(DataflowInvocation dataflowInvocation) { |
| // you are not going to believe this...! |
| for (final WorkflowRun run : provenanceAccess.listRuns( |
| dataflowInvocation.getWorkflowId(), null)) { |
| if (getWorkflowRunId().equals(run.getWorkflowRunId())) { |
| return run.getDataflowBlob(); |
| } |
| } |
| throw new IllegalStateException("Can't find dataflow blob for run " |
| + getWorkflowRunId()); |
| } |
| |
| protected void label(Individual obj, String label) { |
| obj.setLabel(label, EN); |
| } |
| |
| protected Literal timestampToLiteral(Timestamp timestamp) { |
| if (timestamp == null) { |
| return null; |
| } |
| GregorianCalendar cal = new GregorianCalendar(); |
| cal.setTime(timestamp); |
| XMLGregorianCalendar xmlCal = datatypeFactory |
| .newXMLGregorianCalendar(cal); |
| // Chop of the trailing 0-s of non-precission |
| xmlCal.setFractionalSecond(BigDecimal.valueOf( |
| timestamp.getNanos() / 1000000, NANOSCALE - 6)); |
| return provModel.model.createTypedLiteral(xmlCal.toXMLFormat(), |
| XSDDatatype.XSDdateTime); |
| } |
| |
| private static Map<URI, String> mediaTypes = new HashMap<>(); |
| |
| protected void storeFileReferences() { |
| |
| for (Entry<Path, T2Reference> entry : getFileToT2Reference().entrySet()) { |
| Path file = entry.getKey(); |
| |
| try { |
| T2Reference t2Ref = entry.getValue(); |
| URI dataURI = URI.create(uriGenerator.makeT2ReferenceURI(t2Ref |
| .toUri().toASCIIString())); |
| |
| Individual entity = provModel.createArtifact(dataURI); |
| |
| String mediaType = saver.getMediaTypes().get(t2Ref); |
| |
| if (!Files.exists(file)) { |
| continue; |
| } |
| URI contentUri; |
| if (DataBundles.isReference(file)) { |
| // TODO: Do we really need to read this back again from the |
| // file? |
| contentUri = DataBundles.getReference(file); |
| } else { |
| contentUri = toURI(file); |
| } |
| |
| Individual content = provModel.setContent(entity, contentUri); |
| if (mediaType != null) { |
| mediaTypes.put(contentUri, mediaType); |
| } |
| if (!DataBundles.isValue(file)) { |
| // Don't capture the checksum and content of references and |
| // lists |
| continue; |
| } |
| |
| // Add checksums |
| String sha1 = saver.getSha1sums().get(file.toRealPath()); |
| if (sha1 != null) { |
| content.addLiteral(provModel.sha1, sha1); |
| } |
| String sha512 = saver.getSha512sums().get(file.toRealPath()); |
| if (sha512 != null) { |
| content.addLiteral(provModel.sha512, sha512); |
| } |
| long byteCount = Files.size(file); |
| content.addLiteral(provModel.byteCount, byteCount); |
| |
| if (byteCount < EMBEDDED_MAX_FILESIZE) { |
| // Add content if it's "tiny" |
| byte[] bytes = Files.readAllBytes(file); |
| if (mediaType != null && mediaType.startsWith(TEXT)) { |
| // as string - assuming UTF8 (and declaring so) |
| String str = new String(bytes, UTF8); |
| content.addLiteral(provModel.chars, str); |
| content.addLiteral(provModel.characterEncoding, |
| UTF8.name()); |
| content.addRDFType(provModel.ContentAsText); |
| } else { |
| // Or base64-encoded bytes |
| content.addRDFType(provModel.ContentAsBase64); |
| content.addLiteral(provModel.bytes, bytes); |
| } |
| } |
| } catch (IOException e) { |
| logger.warn("Could not read " + file + " as " + UTF8, e); |
| } |
| } |
| } |
| |
| protected URI toURI(Path file) { |
| return file.toUri(); |
| } |
| |
| protected void storeEntitities(String dataBindingId, Individual activity, |
| Direction direction, boolean isTopLevel) throws IOException { |
| |
| Map<Port, T2Reference> bindings = provenanceAccess |
| .getDataBindings(dataBindingId); |
| |
| for (Entry<Port, T2Reference> binding : bindings.entrySet()) { |
| Port port = binding.getKey(); |
| T2Reference t2Ref = binding.getValue(); |
| |
| Individual entity = describeEntity(t2Ref); |
| |
| if (isTopLevel) { |
| Path ports; |
| if (direction == Direction.INPUTS) { |
| ports = DataBundles.getInputs(bundle); |
| } else { |
| ports = DataBundles.getOutputs(bundle); |
| } |
| Path portPath = DataBundles.getPort(ports, port.getPortName()); |
| saveValue(t2Ref, portPath); |
| } else if (!seenReference(t2Ref)) { |
| saveIntermediate(t2Ref); |
| } |
| |
| // String id = t2Ref.getLocalPart(); |
| // String prefix = id.substring(0, 2); |
| Individual involvement; |
| if (direction == Direction.INPUTS) { |
| involvement = provModel.setUsedInput(activity, entity); |
| } else { |
| involvement = provModel.setWasOutputFrom(entity, activity); |
| } |
| |
| String processorName = null; |
| if (port.getProcessorId() != null) { |
| // Not a workflow port |
| ProvenanceProcessor p = provenanceAccess |
| .getProvenanceProcessor(port.getProcessorId()); |
| processorName = p.getProcessorName(); |
| } |
| URI portURI = URI.create(uriGenerator.makePortURI( |
| port.getWorkflowId(), processorName, port.getPortName(), |
| port.isInputPort())); |
| |
| Individual portRole; |
| if (port.isInputPort()) { |
| portRole = provModel.createInputParameter(portURI); |
| } else { |
| portRole = provModel.createOutputParameter(portURI); |
| } |
| |
| portRole.setLabel(port.getPortName(), ""); |
| if (processorName == null) { |
| portRole.setComment( |
| "Workflow" |
| + (port.isInputPort() ? " input " : " output ") |
| + port.getPortName(), EN); |
| } else { |
| portRole.setComment( |
| processorName |
| + (port.isInputPort() ? " input " : " output ") |
| + port.getPortName(), EN); |
| } |
| provModel.setDescribedByParameter(entity, portRole, involvement); |
| |
| } |
| |
| } |
| |
| protected Individual describeEntity(T2Reference t2Ref) throws IOException { |
| URI dataURI = URI.create(uriGenerator.makeT2ReferenceURI(t2Ref.toUri() |
| .toASCIIString())); |
| |
| Individual artifact = describedEntities.get(dataURI); |
| if (artifact != null) { |
| return artifact; |
| } |
| artifact = provModel.createArtifact(dataURI); |
| describedEntities.put(dataURI, artifact); |
| |
| if (t2Ref.getReferenceType() == T2ReferenceType.ErrorDocument) { |
| Individual error = provModel.createError(dataURI); |
| ErrorDocument errorDoc = saver.getReferenceService() |
| .getErrorDocumentService().getError(t2Ref); |
| addMessageIfNonEmpty(error, errorDoc.getMessage()); |
| // getExceptionMEssage added by addStackTrace |
| addStackTrace(error, errorDoc); |
| } else if (t2Ref.getReferenceType() == T2ReferenceType.IdentifiedList) { |
| IdentifiedList<T2Reference> list = saver.getReferenceService() |
| .getListService().getList(t2Ref); |
| Individual dictionary = provModel.createDictionary(dataURI); |
| |
| int pos = 0; |
| for (T2Reference ref : list) { |
| URI itemURI = URI.create(uriGenerator.makeT2ReferenceURI(ref |
| .toUri().toASCIIString())); |
| Individual listItem = provModel.createArtifact(itemURI); |
| provModel.addKeyPair(dictionary, pos++, listItem); |
| describeEntity(ref); |
| } |
| if (list.isEmpty()) { |
| artifact.addRDFType(provModel.EmptyCollection); |
| artifact.addRDFType(provModel.EmptyDictionary); |
| } |
| } |
| |
| return artifact; |
| } |
| |
| private boolean seenReference(T2Reference t2Ref) { |
| return seenReferences.containsKey(t2Ref); |
| } |
| |
| private Path saveIntermediate(T2Reference t2Ref) throws IOException { |
| // Avoid double-saving |
| Path f = seenReferences.get(t2Ref); |
| if (f != null) { |
| return f; |
| } |
| Path file = referencePath(t2Ref); |
| |
| if (t2Ref.getReferenceType() == T2ReferenceType.IdentifiedList) { |
| IdentifiedList<T2Reference> list = saver.getReferenceService() |
| .getListService().getList(t2Ref); |
| for (T2Reference ref : list) { |
| saveIntermediate(ref); |
| } |
| seenReference(t2Ref, file); |
| return file; |
| } else { |
| return saveValue(t2Ref, file); |
| } |
| |
| } |
| |
| private Path saveValue(T2Reference t2Ref, Path file) throws IOException { |
| Path parent = file.getParent(); |
| |
| switch (t2Ref.getReferenceType()) { |
| |
| case IdentifiedList: |
| DataBundles.createList(file); |
| IdentifiedList<T2Reference> list = saver.getReferenceService() |
| .getListService().getList(t2Ref); |
| long position = 0; |
| for (T2Reference ref : list) { |
| saveValue(ref, DataBundles.getListItem(file, position++)); |
| } |
| break; |
| case ErrorDocument: |
| Files.createDirectories(parent); |
| file = saveError(t2Ref, file); |
| break; |
| case ReferenceSet: |
| Files.createDirectories(parent); |
| file = saver.saveReference(t2Ref, file); |
| } |
| seenReference(t2Ref, file); |
| return file; |
| } |
| |
| private Path saveError(T2Reference t2Ref, Path file) throws IOException { |
| ErrorDocument errorDoc = saver.getReferenceService() |
| .getErrorDocumentService().getError(t2Ref); |
| |
| StringBuilder trace = new StringBuilder(); |
| addStackTrace(trace, errorDoc); |
| |
| List<Path> causes = new ArrayList<>(); |
| for (T2Reference cause : errorDoc.getErrorReferences()) { |
| causes.add(saveIntermediate(cause)); |
| } |
| file = DataBundles.setError(file, errorDoc.getMessage(), |
| trace.toString(), causes.toArray(new Path[causes.size()])); |
| return file; |
| } |
| |
| protected void addStackTrace(Individual error, ErrorDocument errorDoc) |
| throws IOException { |
| StringBuilder sb = new StringBuilder(); |
| addStackTrace(sb, errorDoc); |
| if (sb.length() > 0) { |
| error.addLiteral(provModel.stackTrace, sb.toString()); |
| } |
| |
| for (T2Reference errRef : errorDoc.getErrorReferences()) { |
| URI errorURI = URI.create(uriGenerator.makeT2ReferenceURI(errRef |
| .toUri().toASCIIString())); |
| Individual nestedErr = provModel.createError(errorURI); |
| provModel.setWasDerivedFrom(error, nestedErr); |
| describeEntity(errRef); |
| } |
| } |
| |
| protected void addStackTrace(StringBuilder sb, ErrorDocument errorDoc) { |
| if (errorDoc.getExceptionMessage() != null |
| && !errorDoc.getExceptionMessage().isEmpty()) { |
| sb.append(errorDoc.getExceptionMessage()); |
| sb.append("\n"); |
| } |
| if (errorDoc.getStackTraceStrings() == null) { |
| return; |
| } |
| if (sb.length() == 0) { |
| sb.append("Stack trace:\n"); |
| } |
| // Attempt to recreate Java stacktrace style |
| for (StackTraceElementBean trace : errorDoc.getStackTraceStrings()) { |
| sb.append(" at "); |
| sb.append(trace.getClassName()); |
| sb.append("."); |
| sb.append(trace.getMethodName()); |
| sb.append("("); |
| sb.append(trace.getFileName()); |
| sb.append(":"); |
| sb.append(trace.getLineNumber()); |
| sb.append(")"); |
| sb.append("\n"); |
| } |
| } |
| |
| protected void addMessageIfNonEmpty(Individual error, String message) { |
| if (message == null || message.isEmpty()) { |
| return; |
| } |
| error.addLiteral(provModel.errorMessage, message); |
| } |
| |
| private Path referencePath(T2Reference t2Ref) throws IOException { |
| String local = t2Ref.getLocalPart(); |
| try { |
| return DataBundles.getIntermediate(bundle, UUID.fromString(local)); |
| } catch (IllegalArgumentException ex) { |
| return DataBundles.getIntermediates(bundle) |
| .resolve(t2Ref.getNamespacePart()) |
| .resolve(t2Ref.getLocalPart()); |
| } |
| |
| } |
| |
| private boolean seenReference(T2Reference t2Ref, Path file) { |
| getFileToT2Reference().put(file, t2Ref); |
| if (seenReference(t2Ref)) { |
| return true; |
| } |
| return seenReferences.put(t2Ref, file) != null; |
| } |
| |
| public ProvenanceAccess getProvenanceAccess() { |
| return provenanceAccess; |
| } |
| |
| public void setProvenanceAccess(ProvenanceAccess provenanceAccess) { |
| this.provenanceAccess = provenanceAccess; |
| } |
| |
| public String getWorkflowRunId() { |
| return workflowRunId; |
| } |
| |
| public void setWorkflowRunId(String workflowRunId) { |
| this.workflowRunId = workflowRunId; |
| } |
| |
| public void setFileToT2Reference(Map<Path, T2Reference> fileToT2Reference) { |
| this.fileToT2Reference = new HashMap<>(); |
| for (Entry<Path, T2Reference> entry : fileToT2Reference.entrySet()) { |
| seenReference(entry.getValue(), entry.getKey()); |
| } |
| } |
| |
| private static final String WFDESC = "http://purl.org/wf4ever/wfdesc#"; |
| private static WorkflowBundleIO wfBundleIO; |
| private Bundle bundle; |
| private URI runURI; |
| |
| /** |
| * @return the bundle |
| */ |
| public Bundle getBundle() { |
| return bundle; |
| } |
| |
| public void writeBundle(WorkflowBundle wfBundle) throws IOException { |
| |
| Bundle dataBundle = getBundle(); |
| |
| // Workflow |
| DataBundles.setWorkflowBundle(dataBundle, wfBundle); |
| |
| // Generate Manifest |
| // TODO: This should be done automatically on close/save |
| Manifest manifest = new Manifest(dataBundle); |
| manifest.populateFromBundle(); |
| |
| Path workflowRunProvenance = DataBundles |
| .getWorkflowRunProvenance(dataBundle); |
| // Additional metadata |
| manifest.getAggregation(workflowRunProvenance).setMediatype( |
| "text/turtle"); |
| |
| Agent provPlugin = new Agent(); |
| provPlugin.setName("Taverna-PROV plugin, " + applicationConfig.getTitle() + " " + applicationConfig.getName()); |
| provPlugin.setUri(getPluginIdentifier(getClass())); |
| manifest.getAggregation(workflowRunProvenance).setCreatedBy( |
| provPlugin); |
| manifest.setCreatedBy(provPlugin); |
| |
| |
| // Media types: |
| for (Entry<URI, String> e : mediaTypes.entrySet()) { |
| URI uri = e.getKey(); |
| String mediatype = e.getValue(); |
| PathMetadata aggregation = manifest.getAggregation(uri); |
| if (aggregation == null) { |
| // An external reference? Add it. |
| aggregation = manifest.getAggregation(uri); |
| //aggregation = new PathMetadata(); |
| //aggregation.setUri(uri); |
| //manifest.getAggregates().add(aggregation); |
| } |
| aggregation.setMediatype(mediatype); |
| } |
| |
| // Add annotations |
| |
| // This RO Bundle is about a run |
| PathAnnotation bundleAboutRun = new PathAnnotation(); |
| bundleAboutRun.setAbout(runURI); |
| bundleAboutRun.setContent(URI.create("/")); |
| manifest.getAnnotations().add(bundleAboutRun); |
| |
| // Also aggregate the run by ID, and that it was done by taverna |
| Agent taverna = new Agent(); |
| taverna.setName(applicationConfig.getTitle()); |
| taverna.setUri(getTavernaVersion()); |
| manifest.getAggregation(runURI).setCreatedBy(taverna); |
| |
| // TODO: Do we need both the "history" link and the annotation below? |
| manifest.setHistory(Arrays.asList(workflowRunProvenance)); |
| |
| // This RO Bundle is described in the provenance file |
| PathAnnotation provenanceAboutBundle = new PathAnnotation(); |
| provenanceAboutBundle.setAbout(URI.create("/")); |
| provenanceAboutBundle.setContent(URI.create(workflowRunProvenance |
| .toUri().getPath())); |
| manifest.getAnnotations().add(provenanceAboutBundle); |
| |
| // The wfdesc is about the workflow definition |
| Path workflow = DataBundles.getWorkflow(dataBundle); |
| // String workflowType = Files.probeContentType(workflow); |
| manifest.getAggregation(workflow).setMediatype(WORKFLOW_BUNDLE); |
| Path wfdesc = DataBundles.getWorkflowDescription(dataBundle); |
| if (Files.exists(wfdesc)) { |
| PathAnnotation wfdescAboutWfBundle = new PathAnnotation(); |
| wfdescAboutWfBundle |
| .setAbout(URI.create(workflow.toUri().getPath())); |
| wfdescAboutWfBundle |
| .setContent(URI.create(wfdesc.toUri().getPath())); |
| manifest.getAnnotations().add(wfdescAboutWfBundle); |
| } |
| |
| // And the workflow definition is about the workflow |
| PathAnnotation wfBundleAboutWf = new PathAnnotation(); |
| URITools uriTools = new URITools(); |
| URI mainWorkflow = uriTools.uriForBean(wfBundle.getMainWorkflow()); |
| wfBundleAboutWf.setAbout(mainWorkflow); |
| URI wfBundlePath = URI.create(workflow.toUri().getPath()); |
| wfBundleAboutWf.setContent(wfBundlePath); |
| manifest.getAnnotations().add(wfBundleAboutWf); |
| manifest.getAggregation(mainWorkflow); |
| |
| // hasWorkflowDefinition |
| PathAnnotation hasWorkflowDefinition = new PathAnnotation(); |
| hasWorkflowDefinition.setAbout(wfBundlePath); |
| UUID uuid = UUID.randomUUID(); |
| hasWorkflowDefinition.setUri(URI.create("urn:uuid:" + uuid)); |
| Path annotationBody = DataBundles.getAnnotations(dataBundle).resolve( |
| uuid + ".ttl"); |
| hasWorkflowDefinition.setContent(URI.create(annotationBody.toUri() |
| .getPath())); |
| Model model = ModelFactory.createDefaultModel(); |
| URI relPathToWfBundle = uriTools.relativePath(annotationBody.toUri(), |
| workflow.toUri()); |
| model.setNsPrefix("wfdesc", WFDESC); |
| model.add(model.createResource(mainWorkflow.toASCIIString()), |
| model.createProperty(WFDESC + "hasWorkflowDefinition"), |
| model.createResource(relPathToWfBundle.toASCIIString())); |
| try (OutputStream out = Files.newOutputStream(annotationBody)) { |
| model.write(out, "TURTLE", annotationBody.toUri().toASCIIString()); |
| } |
| manifest.getAnnotations().add(hasWorkflowDefinition); |
| |
| PathAnnotation wfBundleAboutWfB = new PathAnnotation(); |
| wfBundleAboutWfB.setAbout(wfBundle.getGlobalBaseURI()); |
| wfBundleAboutWfB.setContent(URI.create(workflow.toUri().getPath())); |
| manifest.getAnnotations().add(wfBundleAboutWfB); |
| |
| manifest.writeAsJsonLD(); |
| |
| // // Saving a data bundle: |
| // Path bundleFile = runPath.getParent().resolve(runPath.getFileName() + |
| // ".bundle.zip"); |
| // DataBundles.closeAndSaveBundle(dataBundle, bundleFile); |
| // NOTE: From now dataBundle and its Path's are CLOSED |
| // and can no longer be accessed |
| |
| } |
| |
| /** Extract our own plugin version - if running within Raven */ |
| protected static URI getPluginIdentifier(Class<?> pluginClass) { |
| ClassLoader classLoader = pluginClass.getClassLoader(); |
| String className = pluginClass.getCanonicalName(); |
| |
| try { |
| // org.osgi.framework.Bundle osgiBundle = FrameworkUtil |
| // .getBundle(pluginClass); |
| // if (osgiBundle != null) { |
| // String symbolicName = osgiBundle.getSymbolicName(); |
| // Version version = osgiBundle.getVersion(); |
| // } |
| |
| // equivalent as above without OSGi dependency: |
| Object bundle = PropertyUtils.getProperty(classLoader, "bundle"); |
| String symbolicName = BeanUtils.getProperty(bundle, "symbolicName"); |
| String version = BeanUtils.getProperty(bundle, "version") |
| .toString(); |
| |
| // NOTE: The above code has not been tested within OSGi as of 2013-12-18 |
| |
| return osgiURI.resolve(uriTools.validFilename(symbolicName) + "/" |
| + uriTools.validFilename(version)); |
| } catch (IllegalAccessException | InvocationTargetException |
| | NullPointerException | NoSuchMethodException e) { |
| // Assume it's not OSGi |
| } |
| |
| // Not OSGi, try as Raven: |
| try { |
| |
| // Artifact artifact = ((LocalArtifactClassLoader) classLoader) |
| // .getArtifact(); |
| // String groupId = artifact.getGroupId(); |
| // String artifactId = artifact.getArtifactId(); |
| // String version = artifact.getVersion(); |
| |
| // Equivalent as above, but without Raven dependency: |
| |
| Object artifact = PropertyUtils |
| .getProperty(classLoader, "artifact"); |
| if (artifact == null) { |
| return null; |
| } |
| // If it worked, then we assume it is a |
| // net.sf.taverna.raven.repository.Artifact |
| // implementation |
| String groupId = BeanUtils.getProperty(artifact, "groupId"); |
| String artifactId = BeanUtils.getProperty(artifact, "artifactId"); |
| String version = BeanUtils.getProperty(artifact, "version"); |
| // mimic scufl2-t2flow |
| return ravenURI.resolve(uriTools.validFilename(groupId) + "/" |
| + uriTools.validFilename(artifactId) + "/" |
| + uriTools.validFilename(version) + "/" |
| + uriTools.validFilename(className)); |
| } catch (IllegalAccessException | InvocationTargetException |
| | NullPointerException | NoSuchMethodException e) { |
| // Assume it's not Raven |
| } |
| |
| // Fallback based on the classname - mimic scufl2-t2flow |
| return ravenURI.resolve("undefined/" + uriTools.validFilename(className)); |
| |
| } |
| |
| public void setBundle(Bundle bundle) { |
| this.bundle = bundle; |
| } |
| |
| } |