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}