001/* Command line program and static methods to manage provenance. 002 * 003 * Copyright (c) 2015 The Regents of the University of California. 004 * All rights reserved. 005 * 006 * '$Author: crawl $' 007 * '$Date: 2018-02-01 20:19:23 +0000 (Thu, 01 Feb 2018) $' 008 * '$Revision: 34655 $' 009 * 010 * Permission is hereby granted, without written agreement and without 011 * license or royalty fees, to use, copy, modify, and distribute this 012 * software and its documentation for any purpose, provided that the above 013 * copyright notice and the following two paragraphs appear in all copies 014 * of this software. 015 * 016 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 017 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 018 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 019 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 020 * SUCH DAMAGE. 021 * 022 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 023 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 024 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 025 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 026 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 027 * ENHANCEMENTS, OR MODIFICATIONS. 028 * 029 */ 030package org.kepler.provenance.manager; 031 032import java.io.File; 033import java.nio.file.Files; 034import java.util.Arrays; 035import java.util.LinkedList; 036import java.util.List; 037import java.util.Map; 038 039import org.apache.taverna.robundle.Bundle; 040import org.apache.taverna.robundle.Bundles; 041import org.kepler.Kepler; 042import org.kepler.kar.KAREntry; 043import org.kepler.kar.KARFile; 044import org.kepler.kar.SaveKAR; 045import org.kepler.kar.handlers.ProvKAREntryHandler; 046import org.kepler.loader.util.ParseWorkflow; 047import org.kepler.objectmanager.cache.CacheManager; 048import org.kepler.objectmanager.lsid.KeplerLSID; 049import org.kepler.provenance.ProvenanceRecorder; 050import org.kepler.provenance.QueryException; 051import org.kepler.provenance.Queryable; 052import org.kepler.provenance.Recording; 053import org.kepler.provenance.RecordingException; 054import org.kepler.provenance.prov.ProvUtilities; 055import org.kepler.provenance.sql.SQLRecordingV8; 056import org.kepler.util.WorkflowRun; 057import org.openprovenance.prov.interop.InteropFramework.ProvFormat; 058 059import ptolemy.kernel.ComponentEntity; 060import ptolemy.kernel.util.IllegalActionException; 061import ptolemy.kernel.util.NameDuplicationException; 062import ptolemy.kernel.util.NamedObj; 063import ptolemy.moml.MoMLParser; 064import ptolemy.moml.filter.BackwardCompatibility; 065import ptolemy.util.MessageHandler; 066 067/** A utility class containing static methods and a command line 068 * interface to manange provenance. 069 * 070 * @author Daniel Crawl 071 * @version $Id: ProvenanceManager.java 34655 2018-02-01 20:19:23Z crawl $ 072 */ 073public class ProvenanceManager { 074 075 /** Delete a run. */ 076 public static void deleteRun(Integer execId) throws Exception { 077 try(Queryable query = _getDefaultQueryable();) { 078 KeplerLSID lsid = query.getExecutionLSIDForExecution(execId); 079 if(lsid == null) { 080 throw new Exception("Execution id " + execId + " not found."); 081 } 082 List<KeplerLSID> lsids = new LinkedList<KeplerLSID>(); 083 lsids.add(lsid); 084 deleteRuns(lsids); 085 } 086 } 087 088 /** Delete runs. */ 089 public static void deleteRuns(List<KeplerLSID> execLSIDs) throws Exception { 090 091 ProvenanceRecorder recorder; 092 Recording recording; 093 try { 094 recorder = new ProvenanceRecorder(); 095 recording = recorder.getRecording(); 096 } catch (IllegalActionException | NameDuplicationException e) { 097 throw new Exception("Error getting default recording interface.", e); 098 } 099 100 // TODO fix for different recording types. 101 if(recording instanceof SQLRecordingV8) { 102 System.out.println("Removing runs: "); 103 for(KeplerLSID lsid: execLSIDs) { 104 System.out.println(lsid); 105 } 106 ((SQLRecordingV8)recording).deleteExecutions(execLSIDs); 107 } else { 108 System.err.println("Cannot delete from recording type " + recording.getClass()); 109 } 110 111 recorder.disconnect(); 112 } 113 114 /** Export all runs to a KAR file. 115 * @param outputFileName the name of the KAR file. 116 * @param delete if true, delete the runs after being exported. 117 */ 118 public static void exportProvenance(String outputFileName, boolean delete) 119 throws Exception { 120 try(Queryable query = _getDefaultQueryable();) { 121 exportProvenance(outputFileName, delete, 122 query.getExecutionLSIDs()); 123 } 124 } 125 126 /** Export a set of provenance runs to a KAR file. 127 * @param outputFileName the name of the KAR file. 128 * @param delete if true, delete each run exported. 129 * @param execId the execution id of the run to export. 130 */ 131 public static void exportProvenance(String outputFileName, boolean delete, int execId) 132 throws Exception { 133 try(Queryable query = _getDefaultQueryable();) { 134 KeplerLSID lsid = query.getExecutionLSIDForExecution(execId); 135 if(lsid == null) { 136 throw new Exception("Could not find LSID for run " + execId); 137 } 138 List<KeplerLSID> lsids = new LinkedList<KeplerLSID>(); 139 lsids.add(lsid); 140 exportProvenance(outputFileName, delete, lsids); 141 } 142 } 143 144 /** Export a set of provenance runs to a KAR file. 145 * @param outputFileName the name of the KAR file. 146 * @param delete if true, delete each run exported. 147 * @param runs the set of KeplerLSID runs to export. 148 */ 149 public static void exportProvenance(String outputFileName, boolean delete, 150 List<KeplerLSID> runs) throws Exception { 151 exportProvenance(outputFileName, delete, runs, "json"); 152 } 153 154 /** Export a set of provenance runs to a KAR file. 155 * @param outputFileName the name of the KAR file. 156 * @param delete if true, delete each run exported. 157 * @param runs the set of KeplerLSID runs to export. 158 * @params format the prov format. 159 */ 160 public static void exportProvenance(String outputFileName, boolean delete, 161 List<KeplerLSID> runs, String format) throws Exception { 162 163 TransferComponent component = new TransferComponent(format); 164 component.setName("ProvenanceTransfer"); 165 component.addExecutions(runs); 166 167 SaveKAR saveKar = new SaveKAR(); 168 saveKar.setFile(new File(outputFileName)); 169 saveKar.addSaveInitiator(component); 170 171 KeplerLSID karLSID = saveKar.saveToDisk(null, null); 172 // check for success 173 if(karLSID == null) { 174 throw new Exception("Error writing KAR."); 175 } 176 177 if(delete) { 178 deleteRuns(runs); 179 } 180 } 181 182 /** Export a research object bundle 183 * 184 */ 185 public static void exportROBundle(String outputFileName, KeplerLSID runLSID) throws Exception { 186 187 File roBundleFile = new File(outputFileName); 188 File workflowFile = File.createTempFile("workflow", ".kar"); 189 File provenanceFile = File.createTempFile("provenance", ".kar"); 190 try { 191 Bundle bundle = Bundles.createBundle(roBundleFile.toPath()); 192 193 // add the workflow 194 exportWorkflow(workflowFile.getAbsolutePath(), runLSID); 195 Files.copy(workflowFile.toPath(), bundle.getRoot().resolve("workflow.kar")); 196 197 // add the prov trace 198 exportProvenance(provenanceFile.getAbsolutePath(), false, 199 Arrays.asList(runLSID), "json"); 200 Files.copy(provenanceFile.toPath(), bundle.getRoot().resolve("provenance.kar")); 201 202 Bundles.setMimeType(bundle, "application/zip"); 203 204 bundle.close(); 205 206 207 } catch(Exception e) { 208 if(roBundleFile.exists() && !roBundleFile.delete()) { 209 System.err.println("WARNING: could not delete " + roBundleFile.getAbsolutePath()); 210 } 211 throw e; 212 } finally { 213 if(!workflowFile.delete()) { 214 System.err.println("WARNING: could not delete " + workflowFile.getAbsolutePath()); 215 } 216 if(!provenanceFile.delete()) { 217 System.err.println("WARNING: could not delete " + provenanceFile.getAbsolutePath()); 218 } 219 } 220 221 } 222 223 /** Export a workflow to a file. 224 * @param outputFileName The name of the workflow file. 225 * @param runLSID The run LSID of the workflow to export. 226 * @return The workflow file 227 */ 228 public static void exportWorkflow(String outputFileName, KeplerLSID runLSID) throws Exception { 229 230 try(Queryable queryable = _getDefaultQueryable()) { 231 232 String momlStr = queryable.getMoMLForExecution(runLSID); 233 234 SaveKAR saveKAR = new SaveKAR(); 235 NamedObj model = ParseWorkflow.parseMoML(momlStr); 236 saveKAR.addSaveInitiator((ComponentEntity<?>)model); 237 File karFile = new File(outputFileName); 238 saveKAR.setFile(karFile); 239 240 KeplerLSID karLSID = saveKAR.saveToDisk(null, null); 241 // check if saved successfully 242 if(karLSID == null) { 243 if(karFile.exists() && !karFile.delete()) { 244 System.err.println("WARNING: could not delete " + 245 karFile.getAbsolutePath()); 246 } 247 throw new Exception("Could not create KAR."); 248 } 249 } 250 } 251 252 /** Import provenance from a KAR file. 253 * @param inputFileName the name of the KAR file to import from. 254 * @param force if true, try to import despite missing any module 255 * dependencies. if false, and there are missing dependencies, throws 256 * an exception. 257 */ 258 public static void importProvenance(String inputFileName, boolean force) 259 throws Exception { 260 261 File inputFile = new File(inputFileName); 262 if(!inputFile.exists()) { 263 throw new Exception("Import file does not exist: " + inputFileName); 264 } 265 266 try(KARFile karFile = new KARFile(inputFile);) { 267 268 if(!force && !karFile.areAllModuleDependenciesSatisfied()) { 269 // TODO 270 throw new Exception("Module dependencies not satisfied and -force not specified."); 271 } 272 273 //karFile.cacheKARContents(); 274 275 ProvKAREntryHandler handler = new ProvKAREntryHandler(); 276 for (KAREntry entry : karFile.karEntries()) { 277 if (handler.handlesType(entry.getType())) { 278 System.out.println("Importing " + entry.getName()); 279 handler.open(karFile, entry, null); 280 } 281 } 282 } 283 } 284 285 /** List the runs. */ 286 public static void listRuns() throws Exception { 287 288 // set the ontology index file so we can show tags. 289 Kepler.setOntologyIndexFile(); 290 291 try(Queryable query = _getDefaultQueryable();) { 292 Map<KeplerLSID, WorkflowRun> runs = 293 query.getWorkflowRunsForExecutionLSIDs(query.getExecutionLSIDs()); 294 295 // TODO add: 296 // duration, start/stop time 297 // status: success, error (and error message) 298 System.out.println("ID, Tag(s), Workflow Name, Execution LSID"); 299 for(WorkflowRun run : runs.values()) { 300 301 String tags = run.getTagsAsFormattedString(); 302 if(tags.trim().isEmpty()) { 303 tags = "<no tags>"; 304 } 305 306 System.out.println(run.getExecId() + ", " + 307 tags + ", " + 308 run.getWorkflowName() + ", " + 309 run.getExecLSID()); 310 } 311 } 312 } 313 314 /** Program entry point. */ 315 public static void main(String[] args) { 316 317 try { 318 if(_parseArgs(args)) { 319 320 if(_operation == Operation.Export || 321 _operation == Operation.Import || 322 _operation == Operation.Graph) { 323 System.setProperty("java.awt.headless", "true"); 324 MoMLParser.setMoMLFilters(BackwardCompatibility.allFilters()); 325 MessageHandler.setMessageHandler(new MessageHandler()); 326 327 // set java properties since they may be referenced in workflows 328 // we will import/export. e.g., module.workflowdir 329 Kepler.setJavaPropertiesAndCopyModuleDirectories(); 330 } 331 332 switch(_operation) { 333 case List: 334 listRuns(); 335 break; 336 337 case Delete: 338 deleteRun(_runId); 339 break; 340 341 case Export: 342 case Import: 343 344 // import/export 345 CacheManager.getInstance().addKAREntryHandler(new ProvKAREntryHandler()); 346 347 if(_operation == Operation.Import) { 348 importProvenance(_inputFileName, _forceImport); 349 } else if(_exportAll) { 350 exportProvenance(_outputFileName, _deleteAfterExport); 351 } else { 352 exportProvenance(_outputFileName, _deleteAfterExport, _runId); 353 } 354 break; 355 356 case Graph: 357 ProvUtilities.writeProv(_outputFileName, _type, _runId); 358 break; 359 } 360 } 361 } catch(Throwable t) { 362 MessageHandler.error("Error managing provenance.", t); 363 System.exit(1); 364 } 365 366 // TODO shut down the database cleanly so the program will exit 367 // for now call System.exit() 368 System.exit(0); 369 } 370 371 /////////////////////////////////////////////////////////////////// 372 //// private methods //// 373 374 /** Get the default queryable. */ 375 private static Queryable _getDefaultQueryable() throws Exception { 376 377 try { 378 return ProvenanceRecorder.getDefaultQueryable(null); 379 } catch (QueryException | RecordingException e) { 380 throw new Exception("Error getting default query interface.", e); 381 } 382 383 } 384 385 /** Parse command line arguments. 386 * @return True if arguments parsed ok, false if an error 387 * and execution should stop. 388 */ 389 private static boolean _parseArgs(String[] args) { 390 391 boolean retval = true; 392 393 String runIdStr = null; 394 395 int i = 0; 396 for(i = 0; i < args.length; i++) { 397 String arg = args[i]; 398 switch(arg) { 399 case "-all": 400 _exportAll = true; 401 break; 402 case "-delete": 403 _deleteAfterExport = true; 404 break; 405 case "-force": 406 _forceImport = true; 407 break; 408 case "-h": 409 case "-help": 410 case "--help": 411 _usage(); 412 return false; 413 case "-i": 414 if(i + 1 == args.length) { 415 System.err.println("ERROR: must provide input file name with -i."); 416 return false; 417 } 418 i++; 419 _inputFileName = args[i]; 420 _operation = Operation.Import; 421 break; 422 case "-g": 423 if(i + 1 == args.length) { 424 System.err.println("ERROR: must provide output file name with -g."); 425 return false; 426 } 427 i++; 428 _outputFileName = args[i]; 429 _operation = Operation.Graph; 430 if(_type == null) { 431 _type = "jpeg"; 432 } 433 break; 434 case "-l": 435 _operation = Operation.List; 436 break; 437 case "-o": 438 if(i + 1 == args.length) { 439 System.err.println("ERROR: must provide output file name with -o."); 440 return false; 441 } 442 i++; 443 _outputFileName = args[i]; 444 _operation = Operation.Export; 445 break; 446 case "-rm": 447 if(i + 1 == args.length) { 448 System.err.println("ERROR: must provide number to delete with -rm."); 449 return false; 450 } 451 i++; 452 runIdStr = args[i]; 453 _operation = Operation.Delete; 454 break; 455 case "-run": 456 if(i + 1 == args.length) { 457 System.err.println("ERROR: must provide number with -run."); 458 return false; 459 } 460 i++; 461 runIdStr = args[i]; 462 break; 463 case "-t": 464 if(i + 1 == args.length) { 465 System.err.println("ERROR: must provide type with -t."); 466 return false; 467 } 468 i++; 469 _type = args[i]; 470 break; 471 default: 472 System.err.println("Unknown argument: " + arg); 473 retval = false; 474 break; 475 } 476 } 477 478 // sanity checks 479 480 if(_operation == null) { 481 System.err.println("Must specify an operation."); 482 retval = false; 483 } else if(_exportAll && runIdStr != null) { 484 System.err.println("Cannot export all (-all) and specific run (-run n)."); 485 retval = false; 486 } else if(_operation == Operation.Export && !_exportAll && runIdStr == null) { 487 System.err.println("Must specify either -all or -run n when exporting."); 488 retval = false; 489 } else if(_deleteAfterExport && _operation != Operation.Export) { 490 System.err.println("Can only use -delete when exporting."); 491 retval = false; 492 } else if(_forceImport && _operation != Operation.Import) { 493 System.err.println("Can only force when importing."); 494 retval = false; 495 } else if(_operation == Operation.Graph && runIdStr == null) { 496 System.err.println("Must specify a specific run when graphing."); 497 retval = false; 498 } 499 500 if(_operation == Operation.Graph) { 501 boolean found = false; 502 for(ProvFormat type : ProvFormat.values()) { 503 if(type.toString().equals(_type.toUpperCase())) { 504 found = true; 505 break; 506 } 507 } 508 if(!found) { 509 System.err.println("Valid graph output types are: "); 510 for(ProvFormat type : ProvFormat.values()) { 511 System.err.print(type.toString().toLowerCase() + " " ); 512 } 513 System.err.println(); 514 retval = false; 515 } 516 } 517 518 519 if(runIdStr != null) { 520 try { 521 _runId = Integer.valueOf(runIdStr); 522 } catch(NumberFormatException e) { 523 System.err.println("ERROR: run " + runIdStr + " is not a number."); 524 retval = false; 525 } 526 if(_runId < 0) { 527 System.err.println("ERROR: run " + _runId + " must be at least 0."); 528 retval = false; 529 } 530 } 531 532 return retval; 533 } 534 535 /** Show command line usage. */ 536 private static void _usage() { 537 538 System.out.println("USAGE: prov-manager [-l | -o ... | -i ... | -rm n | -g ...] [...]"); 539 System.out.println(); 540 System.out.println("-h show this help."); 541 System.out.println("-i ... import from a kar."); 542 System.out.println("-l show provenance runs."); 543 System.out.println("-o ... export to a kar."); 544 System.out.println("-rm n delete workflow run n."); 545 System.out.println(); 546 System.out.println("Export provenance to a KAR file:"); 547 System.out.println("prov-manager -o output.kar [-all | -run n] [-delete]"); 548 System.out.println("-all export all workflows runs."); 549 System.out.println("-run n export run n."); 550 System.out.println("-delete delete workflow runs in database after export."); 551 System.out.println(); 552 System.out.println("Import provenance from a KAR file:"); 553 System.out.println("prov-manager -i input.kar [-force]"); 554 System.out.println("-force attempt to import run(s) despite any missing module dependencies."); 555 System.out.println(); 556 System.out.println("Generate a PROV provenance graph:"); 557 System.out.println("prov-manager -g output -run n [-t jpg|pdf|...]"); 558 System.out.println("-run n graph run n."); 559 System.out.println("-t graph file type."); 560 } 561 562 /////////////////////////////////////////////////////////////////// 563 //// private fields //// 564 565 /** If true, delete the runs after exporting. */ 566 private static boolean _deleteAfterExport = false; 567 568 /** If true, export all the provenance runs. */ 569 private static boolean _exportAll = false; 570 571 /** If true, try to import the runs despite any missing module 572 * dependencies. 573 */ 574 private static boolean _forceImport = false; 575 576 /** The input file name. */ 577 private static String _inputFileName; 578 579 /** The output file name. */ 580 private static String _outputFileName; 581 582 /** The id of the run to delete, export, graph, etc. */ 583 private static Integer _runId; 584 585 /** The types of operations. */ 586 private enum Operation { List, Import, Export, Graph, Delete }; 587 588 /** The operation to perform. */ 589 private static Operation _operation; 590 591 /** The graph type. */ 592 private static String _type; 593}