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}