001/*
002 * Copyright (c) 2017 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2017-08-29 15:24:48 -0700 (Tue, 29 Aug 2017) $' 
007 * '$Revision: 1391 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029package org.kepler.webview.server.handler;
030
031import java.io.ByteArrayOutputStream;
032import java.io.File;
033import java.util.Arrays;
034import java.util.LinkedList;
035import java.util.List;
036import java.util.Map;
037
038import org.kepler.loader.util.Screenshot;
039import org.kepler.objectmanager.lsid.KeplerLSID;
040import org.kepler.provenance.QueryException;
041import org.kepler.provenance.RecordPlayer;
042import org.kepler.provenance.manager.ProvenanceManager;
043import org.kepler.provenance.prov.ProvUtilities;
044import org.kepler.util.WorkflowRun;
045import org.kepler.webview.actor.WebView;
046import org.kepler.webview.server.WebViewServer;
047
048import io.vertx.core.MultiMap;
049import io.vertx.core.http.HttpServerRequest;
050import io.vertx.core.json.JsonArray;
051import io.vertx.core.json.JsonObject;
052import io.vertx.ext.web.RoutingContext;
053import ptolemy.actor.CompositeActor;
054
055/** Handler to get information about a workflow run.
056 * 
057 * @author Daniel Crawl
058 * @version $Id: RunIdHandler.java 1391 2017-08-29 22:24:48Z crawl $
059 * 
060 */
061public class RunIdHandler extends ProvenanceHandler {
062
063    public RunIdHandler(WebViewServer server) {
064        super(server);                
065        Screenshot.closeAllWhenDone(false);        
066    }
067
068    @Override
069    public void handle(RoutingContext context) {
070        
071        HttpServerRequest request = context.request();
072        String runLSID = request.getParam("param0");
073        final MultiMap params = request.params();
074        
075        //System.out.println(runId);
076        
077        /*
078        System.out.println("in runid handler");
079        for(Entry<String, String> e : params) {            
080            System.out.println(e.getKey() + " -> " + e.getValue());
081        } 
082        */
083                
084        _server.getVertx().<JsonObject>executeBlocking(future -> {
085            try {
086                
087                JsonObject responseJson = new JsonObject();                
088                List<KeplerLSID> list = new LinkedList<KeplerLSID>();
089                list.add(new KeplerLSID(runLSID));
090                Map<KeplerLSID, WorkflowRun> runs = _queryable.getWorkflowRunsForExecutionLSIDs(list);
091                
092                // see if the run was found.
093                if(runs.size() == 0) {
094                    future.fail("Bad run id.");
095                } else {                
096                    final WorkflowRun run = runs.values().iterator().next();
097                    
098                    //System.out.println("run user " + run.getUser() + " req user " + 
099                        //context.user().principal().getString("username"));
100                    
101                    // make sure the run belong to the user making the request
102                    if(!run.getUser().equals(context.user().principal().getString("username"))) {
103                        future.fail("Run belongs to different user.");
104                    } else {                   
105                        responseJson.put("status", run.getType())
106                            .put("start", run.getStartTimeISO8601())
107                            .put("workflowName", run.getWorkflowName());
108                        
109                        Map<Integer,String> errorMap = run.getErrorMessages();
110                        if(!errorMap.isEmpty()) {
111                            responseJson.put("runError", errorMap.values().iterator().next());
112                        }
113                                                
114                        if(_getTrueFalseParameter(params, "keysValues")) {
115                            _addKeysValues(responseJson, run);
116                        }
117
118                        if(_getTrueFalseParameter(params, "outputs")) {
119                            _addOutputs(responseJson, run);
120                        }
121                        
122                        if(_getTrueFalseParameter(params, "parametersValues")) {
123                            _addParametersValues(responseJson, run);
124                        }
125                        
126                        if(_getTrueFalseParameter(params, "prov")) {
127                            _addProv(responseJson, params, run);
128                        } else if(params.contains("provFormat")) {
129                           throw new Exception("Must set prov=true when specifying provFormat."); 
130                        }
131                        
132                        future.complete(responseJson);
133                    }
134                }                      
135                
136
137            } catch(Exception e) {
138                future.fail(e);
139            }
140
141        }, false, result -> {            
142            if(result.succeeded()) {
143                _sendResponseWithSuccessJson(request, result.result());
144            } else {
145                _sendResponseWithError(request, result.cause().getMessage());
146            }
147        });   
148    }
149    
150    /** Handle a request to get the workflow for a specific run. */
151    public void handleBinary(RoutingContext context) {
152        
153        //System.out.println("run id handle binary");
154        
155        HttpServerRequest request = context.request();
156        String runLSIDStr = request.getParam("param0");        
157        KeplerLSID runLSID = null;
158
159        try {
160            runLSID = new KeplerLSID(runLSIDStr);
161        } catch(Exception e) {
162            _sendResponseWithError(request, "Bad KeplerLSID: " + runLSIDStr);
163            return;
164        }
165
166        // check permission
167        try {
168            String runUser = _queryable.getUserForExecution(runLSID);
169            if(runUser == null) {
170                _sendResponseWithError(request, "Unable to find user of run.");
171                return;
172            } else if(!runUser.equals(context.user().principal().getString("username"))) {
173                _sendResponseWithError(request, "Unauthorized: run does not belong to user.");                
174                return;
175            }
176        } catch(QueryException e) {
177            _sendResponseWithError(request, e.getMessage());
178            return;
179        }
180
181        String operationStr = request.getParam("param1");
182        final MultiMap params = request.params();
183
184        try {
185
186            if(operationStr.equals("workflow")) {
187                File karFile = null;
188                try {
189                    karFile = File.createTempFile("webviewProvKAR", ".kar");
190                    ProvenanceManager.exportWorkflow(karFile.getAbsolutePath(), runLSID);
191                    _sendResponseWithFile(request, karFile, true);
192                    karFile = null;
193                } finally {
194                    if(karFile != null && !karFile.delete()) {
195                        System.err.println("WARNING: unable to delete " + karFile.getAbsolutePath());
196                    }
197                }
198            } else if(operationStr.equals("screenshot")) {
199                File karFile = null;
200                try {
201                    karFile = File.createTempFile("webviewProvKAR", ".kar");
202                    ProvenanceManager.exportWorkflow(karFile.getAbsolutePath(), runLSID);
203                    List<File> screenshots = Screenshot.makeScreenshot(
204                            Arrays.asList(karFile.getAbsolutePath()),
205                            "png",
206                            null,
207                            null, 
208                            false);
209                    if(screenshots.isEmpty()) {
210                        _sendResponseWithError(request, "Unable to make screenshot.");
211                    } else {
212                        _sendResponseWithFile(request, screenshots.get(0), true);
213                    }                    
214                } finally {
215                    if(karFile != null && !karFile.delete()) {
216                        System.err.println("WARNING: unable to delete " + karFile.getAbsolutePath());
217                    }
218                }
219            } else if(operationStr.equals("prov")) {
220                String formatStr = _getProvFormat(params);
221                File karFile = null;
222                try {
223                    karFile = File.createTempFile("webviewProvKAR", ".kar");
224                    ProvenanceManager.exportProvenance(karFile.getAbsolutePath(),
225                        false, Arrays.asList(runLSID), formatStr);
226                    _sendResponseWithFile(request, karFile, true);
227                    // file will be deleted after response sent
228                    karFile = null;
229                } finally {
230                    if(karFile != null && !karFile.delete()) {
231                        System.err.println("WARNING: unable to delete " + karFile.getAbsolutePath());
232                    }
233                }
234            } else if(operationStr.equals("roBundle")) {
235                File roBundleFile = null;
236                try {
237                    roBundleFile = File.createTempFile("webviewROBundle", ".zip");
238                    ProvenanceManager.exportROBundle(roBundleFile.getAbsolutePath(), runLSID);
239                    _sendResponseWithFile(request, roBundleFile, true);
240                    // file will be deleted after response sent
241                    roBundleFile = null;
242                } finally {
243                    if(roBundleFile != null && !roBundleFile.delete()) {
244                        System.err.println("WARNING: unable to delete " + roBundleFile.getAbsolutePath());    
245                    }
246                }
247            } else {
248                _sendResponseWithError(request, "Unsupported operation: " + operationStr);
249            }
250        } catch (Exception e) {
251            System.err.println("Exception: " + e.getMessage());
252            _sendResponseWithError(request, e.getMessage());
253        }
254    }
255    
256    /** Add keys-values. */
257    private void _addKeysValues(JsonObject responseJson, WorkflowRun run) throws Exception {
258            
259        JsonObject keysValuesJson = new JsonObject();
260        responseJson.put("keysValues", keysValuesJson);
261        Map<String,String> keysValues = _queryable.getAssociatedKeysValuesForExecution(run.getExecId());
262        for(Map.Entry<String,String> entry: keysValues.entrySet()) {
263            keysValuesJson.put(entry.getKey(), entry.getValue());
264        }
265    }
266
267    /** Add workflow outputs. */
268    private void _addOutputs(JsonObject responseJson, WorkflowRun run) throws Exception {
269
270        CompositeActor model = (CompositeActor)_queryable.getWorkflowForExecution(run.getExecId());
271        WebViewServer._addModel(model);
272        
273        try {                                
274            RecordPlayer player = new RecordPlayer(_queryable);
275            player.executeActor(WebView.class.getName());
276            player.play(run.getExecId(), model);
277            
278            // build the response JSON object from the client buffers
279            // for this workflow.
280            JsonArray arrayJson = new JsonArray();
281            List<JsonObject> outputs = WebViewServer.getClientBuffer(model);
282            if(outputs != null) {
283                for(JsonObject outputJson : outputs) {
284                    if(outputJson.containsKey("actor")) {
285                        JsonObject actorObject = outputJson.getJsonObject("actor");
286                        for(String idStr : actorObject.fieldNames()) {
287                            JsonObject idObject = actorObject.getJsonObject(idStr);
288                            if(idObject.containsKey("data")) {
289                                arrayJson.add(idObject.getJsonObject("data"));
290                            }
291                        }
292                    }
293                }
294            }
295            // send the successful response
296            //System.out.println(arrayJson.encodePrettily());
297            responseJson.put("responses", arrayJson);
298            
299        } finally {
300            WebViewServer.removeModel(model);
301        }
302    }
303    
304    /** Add parameters-values. */
305    private void _addParametersValues(JsonObject responseJson, WorkflowRun run) throws Exception {
306            
307        JsonObject parametersValuesJson = new JsonObject();
308        responseJson.put("parametersValues", parametersValuesJson);
309        Map<String,String> parametersValues = _queryable.getParameterNameValuesOfSpecificTypeForExecution(run.getExecId());
310        for(Map.Entry<String,String> entry: parametersValues.entrySet()) {
311            parametersValuesJson.put(entry.getKey(), entry.getValue());
312        }
313    }
314
315    /** Add prov if prov parameter is true. */
316    private void _addProv(JsonObject responseJson, MultiMap params,
317            WorkflowRun run) throws Exception {
318
319        String formatStr = _getProvFormat(params);
320        
321        try(ByteArrayOutputStream stream = new ByteArrayOutputStream();) {
322            ProvUtilities.writeProv(stream, formatStr, run.getExecId());
323            responseJson.put("prov", stream.toString());
324        }
325    }
326    
327    /** Get the PROV format from request params. */
328    private String _getProvFormat(MultiMap params) {
329        String formatStr = params.get("provFormat");
330        if(formatStr == null) {
331            return "JSON";
332        }
333        return formatStr.toUpperCase();
334    }
335        
336    /** Verify that a REST parameter is either true or false.
337     * @param params the REST parameters
338     * @param the name of the parameter
339     * @return the value of the parameter
340     */
341    private boolean _getTrueFalseParameter(MultiMap params, String name)
342        throws Exception {
343        String value = params.get(name);
344        if(value == null) {
345            return false;
346        }
347        if(value != null && !"true".equals(value) && !"false".equals(value)) {
348            throw new Exception("Parameter " + name + " must be either true or false.");
349        }
350        return value.toLowerCase().equals("true");
351    }
352        
353}