001/* 002 * 003 * Copyright (c) 2015 The Regents of the University of California. 004 * All rights reserved. 005 * 006 * '$Author: crawl $' 007 * '$Date: 2017-08-23 22:42:39 -0700 (Wed, 23 Aug 2017) $' 008 * '$Revision: 1375 $' 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.webview.server.handler; 031 032import java.io.File; 033import java.io.IOException; 034import java.net.HttpURLConnection; 035import java.util.HashSet; 036import java.util.List; 037import java.util.Set; 038 039import org.apache.commons.io.FileUtils; 040import org.kepler.webview.actor.WebViewable; 041import org.kepler.webview.server.WebViewId; 042import org.kepler.webview.server.WebViewServer; 043import org.kepler.webview.server.WebViewableUtilities; 044 045import io.vertx.core.http.HttpServerRequest; 046import io.vertx.core.http.HttpServerResponse; 047import io.vertx.core.json.JsonObject; 048import io.vertx.ext.web.RoutingContext; 049import ptolemy.kernel.CompositeEntity; 050import ptolemy.kernel.Entity; 051import ptolemy.kernel.util.IllegalActionException; 052import ptolemy.kernel.util.NamedObj; 053import ptolemy.kernel.util.StringAttribute; 054import ptolemy.util.MessageHandler; 055 056public class WorkflowHandler extends BaseHandler { 057 058 public WorkflowHandler(WebViewServer server) { 059 super(server); 060 } 061 062 @Override 063 public void handle(RoutingContext context) { 064 065 final long timestamp = System.currentTimeMillis(); 066 067 HttpServerRequest req = context.request(); 068 HttpServerResponse response = context.response(); 069 response.headers().set("Content-Type", "text/html"); 070 response.setChunked(true); 071 072 String id = req.params().get("param0"); 073 //System.out.println("wf name is: " + name); 074 NamedObj model = WebViewId.getNamedObj(id); 075 if (model == null) { 076 response.write("<html>\n<body>\n<h2>Workflow " + 077 id + " not found.</h2>\n</body>\n</html>") 078 .setStatusCode(HttpURLConnection.HTTP_NOT_FOUND).end(); 079 System.err.println("Unhandled http request (workflow not found) for: " + 080 req.path()); 081 _server.log(req, context.user(), HttpURLConnection.HTTP_NOT_FOUND, timestamp); 082 return; 083 } 084 085 // TODO make sure is top level composite actor. 086 087 String wfStartPath = WebViewServer.findFile(_WF_START); 088 if(wfStartPath == null) { 089 response.write("<html>\n<body>\n<h2>wf-start.html " + 090 " not found.</h2>\n</body>\n</html>") 091 .setStatusCode(HttpURLConnection.HTTP_NOT_FOUND).end(); 092 System.err.println("Unhandled http request (wf-start.html not found) for: " + 093 req.path()); 094 _server.log(req, context.user(), HttpURLConnection.HTTP_NOT_FOUND, timestamp); 095 return; 096 } 097 098 String wfEndPath = WebViewServer.findFile(_WF_END); 099 if(wfEndPath == null) { 100 response.write("<html>\n<body>\n<h2>wf-end.html " + 101 " not found.</h2>\n</body>\n</html>") 102 .setStatusCode(HttpURLConnection.HTTP_NOT_FOUND).end(); 103 System.err.println("Unhandled http request (wf-end.html not found) for: " + 104 req.path()); 105 _server.log(req, context.user(), HttpURLConnection.HTTP_NOT_FOUND, timestamp); 106 return; 107 } 108 109 StringBuilder buf = new StringBuilder(); 110 111 try { 112 113 buf.append(FileUtils.readFileToString(new File(wfStartPath))); 114 115 Set<String> idsSeen = new HashSet<String>(); 116 List<WebViewable> webViews = _getWebViewables(model); 117 for (WebViewable webView : webViews) { 118 119 120 String webViewId; 121 try { 122 webViewId = WebViewId.getId((NamedObj)webView); 123 } catch (IllegalActionException e) { 124 System.err.println("Error getting id for " + webView.getFullName() + ": " + e.getMessage()); 125 // TODO do not try to add position for the webviewable below 126 continue; 127 } 128 129 System.out.println("found actor: " + webView.getName() + ", id = " + webViewId); 130 131 // since ids may be re-used, only add one iframe for each id. 132 if(!idsSeen.contains(webViewId)) { 133 String title = WebViewableUtilities.getTitle(webView); 134 135 _addIFrame(buf, webViewId, title); 136 137 idsSeen.add(webViewId); 138 } 139 } 140 141 String end = FileUtils.readFileToString(new File(wfEndPath)); 142 // TODO get rid of ugliness below 143 end = end.replaceAll("URL", 144 "'ws://'" 145 + " + window.location.hostname" 146 + " + ':'" 147 + " + window.location.port" 148 + " + '/ws/" 149 + id 150 + "'"); 151 152 StringBuilder positionBuf = new StringBuilder(); 153 for(WebViewable webView : webViews) { 154 155 try { 156 // since ids may be re-used, only set the position 157 // for an id once. 158 if(idsSeen.remove(WebViewId.getId((NamedObj)webView))) { 159 try { 160 _addPosition(positionBuf, webView); 161 } catch (IllegalActionException e) { 162 System.err.println("Error setting posibiotn for " + webView.getFullName() + ": " + e.getMessage()); 163 } 164 } 165 } catch (IllegalActionException e) { 166 System.err.println("Error getting id for " + webView.getFullName() + ": " + e.getMessage()); 167 // TODO do not try to add position for the webviewable below 168 continue; 169 } 170 } 171 172 end = end.replace("POSITION_ACTORS", positionBuf.toString()); 173 174 buf.append(end); 175 response.write(buf.toString()); 176 177 } catch(IOException e) { 178 MessageHandler.error("Error reading file.", e); 179 } 180 response.setStatusCode(HttpURLConnection.HTTP_OK) 181 .end(); 182 183 _server.log(req, context.user(), HttpURLConnection.HTTP_OK, timestamp); 184 185 } 186 187 private static void _addIFrame(StringBuilder buf, String id, String title) { 188 buf.append(" <div id=\"") 189 .append(id) 190 .append("\" class=\"actor\" title=\"") 191 .append(title) 192 .append("\">\n") 193 .append(" <iframe id=\"iframe-") 194 .append(id) 195 .append("\" src=\"/wf/") 196 .append(id) 197 .append("\"></iframe>\n") 198 .append(" </div>\n"); 199 } 200 201 private static void _addPosition(StringBuilder buf, WebViewable obj) throws IllegalActionException { 202 203 String id = WebViewId.getId((NamedObj)obj); 204 205 int topOffset = 1; 206 int leftOffset = 1; 207 208 String topOffsetStr = null; 209 String leftOffsetStr = null; 210 Number width = null; 211 Number height = null; 212 213 StringAttribute posAttribute = (StringAttribute) obj.getAttribute("_webWindowProperties"); 214 if(posAttribute != null) { 215 String value = null; 216 value = posAttribute.getValueAsString(); 217 218 if(value != null && !value.trim().isEmpty()) { 219 JsonObject json = new JsonObject(value); 220 if(json.containsKey("top")) { 221 topOffsetStr = "top+" + json.getInteger("top").toString(); 222 } 223 if(json.containsKey("left")) { 224 leftOffsetStr = "left+" + json.getInteger("left").toString(); 225 } 226 if(json.containsKey("width")) { 227 width = json.getInteger("width"); 228 } 229 if(json.containsKey("height")) { 230 height = json.getInteger("height"); 231 } 232 } 233 } 234 235 if(topOffsetStr == null) { 236 topOffsetStr = topOffset * 10 + "%"; 237 topOffset++; 238 } 239 240 if(leftOffsetStr == null) { 241 leftOffsetStr = leftOffset * 10 + "%"; 242 leftOffset++; 243 } 244 245 // FIXME do dashes need to be escaped?? 246 247 buf.append(" $('#") 248 .append(id) 249 // NOTE: must be 'left top', not 'top left' 250 .append("').dialog('option', 'position', { my: 'left top', at: '") 251 .append(leftOffsetStr) 252 .append(" ") 253 .append(topOffsetStr) 254 // NOTE: of: is required; if not specified position is not set. 255 .append("', of: window});\n"); 256 257 if(width != null && height != null) { 258 buf.append(" $('#") 259 .append(id) 260 .append("').dialog('option', 'width', ") 261 .append(width) 262 .append(")\n") 263 .append(" .dialog('option', 'height', ") 264 .append(height) 265 .append(");\n") 266 .append(" $('#iframe-") 267 .append(id) 268 .append("').load(function() {\n") 269 .append(" resize('") 270 .append("iframe-") 271 .append(id) 272 .append("', ") 273 .append(width) 274 .append(", ") 275 .append(height) 276 // TODO remove constant 277 .append("- 10);\n") 278 .append(" });\n\n"); 279 } 280 } 281 282 /** Get all the WebViewables in a NamedObj. */ 283 private static List<WebViewable> _getWebViewables(NamedObj namedObj) { 284 285 // add attributes 286 List<WebViewable> webViews = namedObj.attributeList(WebViewable.class); 287 for(NamedObj subNamedObj : namedObj.attributeList(NamedObj.class)) { 288 webViews.addAll(_getWebViewables(subNamedObj)); 289 } 290 291 // add actors 292 if(namedObj instanceof CompositeEntity) { 293 webViews.addAll(((CompositeEntity)namedObj).entityList(WebViewable.class)); 294 for(Entity<?> entity : ((CompositeEntity)namedObj).entityList(Entity.class)) { 295 webViews.addAll(_getWebViewables(entity)); 296 } 297 } 298 return webViews; 299 } 300 301 private static final String _WF_START = "web-view" + 302 File.separator + "wf-start.html"; 303 304 private static final String _WF_END = "web-view" + 305 File.separator + "wf-end.html"; 306}