001/* 002 003Copyright (c) 2017 The Regents of the University of California. 004All rights reserved. 005Permission is hereby granted, without written agreement and without 006license or royalty fees, to use, copy, modify, and distribute this 007software and its documentation for any purpose, provided that the above 008copyright notice and the following two paragraphs appear in all copies 009of this software. 010 011IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015SUCH DAMAGE. 016 017THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022ENHANCEMENTS, OR MODIFICATIONS. 023 024*/ 025package org.kepler.provenance.actor; 026 027import java.io.BufferedReader; 028import java.io.IOException; 029import java.io.InputStream; 030import java.io.InputStreamReader; 031import java.util.HashMap; 032import java.util.LinkedList; 033import java.util.List; 034import java.util.Map; 035import java.util.regex.Matcher; 036import java.util.regex.Pattern; 037 038import org.kepler.provenance.ProvenanceEvent; 039 040import ptolemy.actor.TypedAtomicActor; 041import ptolemy.actor.TypedIOPort; 042import ptolemy.actor.parameters.PortParameter; 043import ptolemy.data.StringToken; 044import ptolemy.data.expr.StringParameter; 045import ptolemy.data.type.BaseType; 046import ptolemy.kernel.CompositeEntity; 047import ptolemy.kernel.util.IllegalActionException; 048import ptolemy.kernel.util.NameDuplicationException; 049 050/** An actor that adds key-value data to provenance. The key-value data 051 * is obtained by running an external program specified in the <i>exec</i> 052 * parameter. The key-values are associated with the current execution of 053 * the workflow. 054 * 055 * @author Daniel Crawl 056 * @version $Id: AddProvenanceKeyValues.java 34602 2017-08-11 21:05:08Z crawl $ 057 */ 058public class AddProvenanceKeyValues extends TypedAtomicActor { 059 060 public AddProvenanceKeyValues(CompositeEntity container, String name) 061 throws IllegalActionException, NameDuplicationException { 062 super(container, name); 063 064 exec = new StringParameter(this, "exec"); 065 066 input = new PortParameter(this, "input"); 067 input.getPort().setTypeEquals(BaseType.STRING); 068 input.setTypeEquals(BaseType.STRING); 069 070 output = new TypedIOPort(this, "output", false, true); 071 output.setTypeEquals(BaseType.STRING); 072 073 } 074 075 076 @Override 077 public void fire() throws IllegalActionException { 078 super.fire(); 079 080 List<String> command = new LinkedList<String>(); 081 082 command.add(((StringToken)exec.getToken()).stringValue()); 083 084 input.update(); 085 086 StringToken inputToken = (StringToken) input.getToken(); 087 if(inputToken != null) { 088 command.add(inputToken.stringValue()); 089 } 090 091 // execute the external process 092 try { 093 Process process = new ProcessBuilder(command).start(); 094 095 // read the stdout 096 try(InputStream istream = process.getInputStream(); 097 InputStreamReader isreader = new InputStreamReader(istream); 098 BufferedReader bufReader = new BufferedReader(isreader);) { 099 100 Map<String,String> map = new HashMap<String,String>(); 101 102 String line = bufReader.readLine(); 103 while(line != null) { 104 105 // try to parse the key-values 106 Matcher matcher = KEY_VALUE_PATTERN.matcher(line); 107 if(!matcher.matches()) { 108 System.err.println("WARNING: line does not match key = value: " + line); 109 } else { 110 map.put(matcher.group(1).trim(), matcher.group(2).trim()); 111 } 112 line = bufReader.readLine(); 113 } 114 115 // send the event to provenance recorder 116 _debug(new ProvenanceEvent(this, map)); 117 118 } 119 120 process.waitFor(); 121 122 } catch (IOException | InterruptedException e) { 123 throw new IllegalActionException(this, e, 124 "Error executing command: " + e.getMessage()); 125 } 126 } 127 128 @Override 129 public void preinitialize() throws IllegalActionException { 130 super.preinitialize(); 131 132 // make sure exec is not empty. 133 StringToken execToken = (StringToken)exec.getToken(); 134 if(execToken == null || execToken.stringValue().trim().isEmpty()) { 135 throw new IllegalActionException(this, "exec must be specified"); 136 } 137 } 138 139 /** Name of executable that generates key-value pairs. */ 140 public StringParameter exec; 141 142 /** An optional input argument to executable. */ 143 public PortParameter input; 144 145 /** The value from the input port is copied when execution is complete. */ 146 public TypedIOPort output; 147 148 /** Regex pattern for key-value output from the executable. */ 149 private static final Pattern KEY_VALUE_PATTERN = Pattern.compile("([^=]+)=(.+)"); 150}