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}