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}