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:27:08 -0700 (Tue, 29 Aug 2017) $' 007 * '$Revision: 1392 $' 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 */ 029 030package org.kepler.webview.server; 031 032import java.util.Date; 033import java.util.LinkedList; 034import java.util.List; 035 036import org.kepler.sms.SemanticType; 037import org.kepler.webview.actor.WebViewable; 038 039import io.vertx.core.json.JsonObject; 040import io.vertx.core.shareddata.LocalMap; 041import ptolemy.data.StringToken; 042import ptolemy.data.Token; 043import ptolemy.data.expr.Parameter; 044import ptolemy.data.expr.StringParameter; 045import ptolemy.kernel.util.IllegalActionException; 046import ptolemy.kernel.util.NamedObj; 047 048public class WebViewableUtilities { 049 050 private WebViewableUtilities() { 051 } 052 053 public static String getTitle(WebViewable webView) { 054 String title = null; 055 StringParameter titleParameter = (StringParameter) webView.getAttribute("title"); 056 if(titleParameter != null) { 057 // TODO use getToken instead. 058 title = titleParameter.getExpression(); 059 } 060 if(title == null || title.trim().isEmpty()) { 061 title = webView.getName(webView.toplevel()); 062 } 063 return title; 064 } 065 066 public static void sendData(JsonObject jsonData, NamedObj source) throws IllegalActionException { 067 068 final String actorId = WebViewId.getId(source); 069 070 JsonObject name = new JsonObject().put("data", jsonData); 071 JsonObject actor = new JsonObject().put(actorId, name); 072 JsonObject json = new JsonObject().put("actor", actor); 073 074 _sendToClients(json, source); 075 } 076 077 public static void sendEvent(Event type, NamedObj source) throws IllegalActionException { 078 sendEvent(type, source, new Date()); 079 } 080 081 public static void sendEvent(Event type, NamedObj source, Date timestamp) throws IllegalActionException { 082 _sendEvent(type, source, timestamp, null); 083 } 084 085 private static void _sendEvent(Event type, NamedObj source, Date timestamp, 086 String clientId) throws IllegalActionException { 087 088 if(timestamp == null) { 089 timestamp = new Date(); 090 } 091 092 JsonObject event = new JsonObject() 093 .put("type", type._value) 094 .put("ts", timestamp.getTime()); 095 JsonObject json = new JsonObject().put("event", event); 096 097 if(type == Event.FireStart || type == Event.FireEnd || 098 type == Event.Initialize) { 099 final String actorIdStr = WebViewId.getId(source); 100 event.put("actor", actorIdStr); 101 } 102 103 _sendToClient(json, source, clientId); 104 } 105 106 public static void sendEvent(Event type, NamedObj source, String id) throws IllegalActionException { 107 _sendEvent(type, source, null, id); 108 } 109 110 public static void sendOptions(NamedObj source, String id) throws IllegalActionException { 111 112 List<Parameter> parameters = new LinkedList<Parameter>(); 113 if(source instanceof WebViewable) { 114 parameters = ((WebViewable)source).getOptions(); 115 } else { 116 for(Parameter parameter : source.attributeList(Parameter.class)) { 117 parameters.add(parameter); 118 } 119 } 120 121 JsonObject options = new JsonObject(); 122 for(Parameter parameter : parameters) { 123 String name = parameter.getName(); 124 // TODO do not include parameters containing WebViewables 125 if(!name.startsWith("_") && 126 !name.equals("html") && 127 !name.equals("htmlFile") && 128 !name.equals("title") && 129 !(parameter instanceof SemanticType)) { 130 Token token = parameter.getToken(); 131 if(token != null) { 132 133 JsonObject parameterJson = new JsonObject(); 134 135 if(token instanceof StringToken) { 136 parameterJson.put("value", ((StringToken)token).stringValue()); 137 parameterJson.put("datatype", "string"); 138 } else { 139 // TODO use put methods for numbers? 140 parameterJson.put("value", token.toString()); 141 // TODO other types? records? 142 parameterJson.put("datatype", "num"); 143 } 144 145 for(Parameter subParameter : parameter.attributeList(Parameter.class)) { 146 if(!(subParameter instanceof SemanticType)) { 147 Token subToken = subParameter.getToken(); 148 if(subToken != null) { 149 if(token instanceof StringToken) { 150 parameterJson.put(subParameter.getName(), ((StringToken)subToken).stringValue()); 151 } else { 152 parameterJson.put(subParameter.getName(), subToken.toString()); 153 } 154 } 155 } 156 } 157 158 options.put(name, parameterJson); 159 } 160 } 161 } 162 163 // only send if options is not empty 164 if(options.size() > 0) { 165 final String actorId = WebViewId.getId(source); 166 _sendToClient(new JsonObject().put("actor", 167 new JsonObject().put(actorId, 168 new JsonObject().put("options", options))), source, id); 169 } 170 } 171 172 // sent when id is changed. 173 /* FIXME used any more? 174 public static void sendRenameActor(String oldName, NamedObj source) throws IllegalActionException { 175 176 // TODO id does not change when rename occurs. 177 final String newName = WebViewId.getId(source); 178 179 JsonObject rename = new JsonObject() 180 .putString("oldName", oldName) 181 .putString("newName", newName); 182 JsonObject json = new JsonObject().putObject("actor_rename", rename); 183 184 _sendToClients(json, source); 185 } 186 */ 187 188 public static void sendTitle(String title, NamedObj source) throws IllegalActionException { 189 190 final String actorId = WebViewId.getId(source); 191 192 JsonObject name = new JsonObject().put("title", title); 193 JsonObject actor = new JsonObject().put(actorId, name); 194 JsonObject json = new JsonObject().put("actor", actor); 195 196 _sendToClients(json, source); 197 } 198 199 public enum Event { 200 FireStart("fire_start"), 201 FireEnd("fire_end"), 202 Initialize("initialize"), 203 WorkflowClosed("wf_closed"), 204 WorkflowExecutionStart("wf_start"), 205 WorkflowExecutionEnd("wf_end"); 206 207 private Event(String value) { 208 _value = value; 209 } 210 211 private String _value; 212 }; 213 214 private static void _sendToClients(JsonObject json, NamedObj source) { 215 _sendToClient(json, source, null); 216 } 217 218 219 /** Send data to any clients of a NamedObj. */ 220 private static void _sendToClient(JsonObject json, NamedObj source, String clientId) { 221 222 if(clientId != null) { 223 WebViewServer.vertx().eventBus().publish(clientId, json.encode()); 224 return; 225 } 226 227 final NamedObj model = source.toplevel(); 228 229 // save in client buffer 230 231 List<JsonObject> clientBuffer = WebViewServer.getClientBuffer(model); 232 if(clientBuffer == null) { 233 System.err.println("no buffer for " + model.getFullName() + " " + model.getClassName()); 234 return; 235 } 236 237 // synchronize on the buffer so WorkflowWebSocketHandler 238 // can send any buffered data to a newly-connected client 239 // before new data arrives. 240 synchronized(clientBuffer) { 241 242 // ignore json if actor rename event 243 if(!json.containsKey("actor_rename")) { 244 245 // see if json is wf_start event 246 if(json.containsKey("event") && 247 json.getJsonObject("event").getString("type").equals("wf_start")) { 248 249 // clear buffer 250 clientBuffer.clear(); 251 //System.out.println("cleared buffer"); 252 } 253 254 // save json to buffer 255 //System.out.println("buffering " + json.encode()); 256 clientBuffer.add(json); 257 } 258 259 // send to any web socket clients 260 String modelId; 261 try { 262 modelId = WebViewId.getId(model); 263 } catch (IllegalActionException e) { 264 System.err.println("Unable to get id for " + model.getFullName() + ": " + e.getMessage()); 265 return; 266 } 267 LocalMap<String,Integer> map = WebViewServer.vertx().sharedData().getLocalMap("/ws/" + modelId); 268 for(String id : map.keySet()) { 269 //System.out.println("sending to " + id); 270 WebViewServer.vertx().eventBus().publish(id, json.encode()); 271 } 272 } 273 } 274}