001/*
002 * Copyright (c) 2014 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2011-04-12 13:56:18 -0700 (Tue, 12 Apr 2011) $' 
007 * '$Revision: 27498 $'
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.cloudsharing.gui;
030
031import java.awt.Frame;
032import java.awt.GridBagConstraints;
033import java.awt.GridBagLayout;
034import java.awt.event.ActionEvent;
035import java.awt.event.ActionListener;
036import java.io.ByteArrayInputStream;
037import java.io.DataOutputStream;
038import java.io.File;
039import java.io.FileOutputStream;
040import java.io.FileWriter;
041import java.io.IOException;
042import java.io.OutputStream;
043import java.util.HashMap;
044import java.util.HashSet;
045import java.util.List;
046import java.util.Map;
047
048import javax.swing.AbstractAction;
049import javax.swing.ButtonGroup;
050import javax.swing.JButton;
051import javax.swing.JCheckBox;
052import javax.swing.JDialog;
053import javax.swing.JLabel;
054import javax.swing.JPanel;
055import javax.swing.JRadioButton;
056import javax.swing.JTextField;
057
058import org.kepler.cloudsharing.actor.ShareToCloud;
059import org.kepler.cloudsharing.fs.FileSystemClient;
060import org.kepler.cloudsharing.util.WorkflowResults;
061import org.kepler.gui.KeplerGraphFrame;
062import org.kepler.gui.kar.SaveArchiveAction;
063import org.kepler.kar.KARFile;
064import org.kepler.kar.KARManager;
065import org.kepler.moml.NamedObjId;
066import org.kepler.objectmanager.lsid.KeplerLSID;
067import org.kepler.provenance.ProvenanceRecorder;
068import org.kepler.provenance.Queryable;
069import org.kepler.reporting.rio.ReportInstance;
070import org.kepler.reporting.rio.fop.ReportRenderer;
071import org.seedme.client.FileEntry;
072import org.seedme.client.SeedMe;
073
074import com.google.common.io.Files;
075
076import ptolemy.actor.gui.BrowserLauncher;
077import ptolemy.actor.gui.Effigy;
078import ptolemy.actor.gui.ModelDirectory;
079import ptolemy.actor.gui.PlotTableauFrame;
080import ptolemy.actor.gui.PtolemyEffigy;
081import ptolemy.actor.gui.Tableau;
082import ptolemy.actor.gui.TextEditor;
083import ptolemy.data.StringToken;
084import ptolemy.data.Token;
085import ptolemy.data.expr.Parameter;
086import ptolemy.gui.ImageExportable;
087import ptolemy.kernel.CompositeEntity;
088import ptolemy.kernel.util.IllegalActionException;
089import ptolemy.kernel.util.NamedObj;
090import ptolemy.util.CancelException;
091import ptolemy.util.MessageHandler;
092
093/** A dialog to upload and share workflow results to the cloud.
094 * 
095 *  @author Daniel Crawl
096 *  @version $Id$
097 * 
098 */
099public class ShareResultsToCloudDialog extends JDialog implements ActionListener {
100
101    public ShareResultsToCloudDialog(Frame owner) {
102        
103        super(owner, "Share Workflow Results To Cloud");
104        
105        _parent = (KeplerGraphFrame) owner;
106        String workflowName = _parent.getModel().getName();
107                
108        JPanel p = new JPanel(new GridBagLayout());
109        add(p);
110        
111        GridBagConstraints c = new GridBagConstraints();
112        
113        int y = 0;
114        
115        c.gridx = 0;
116        c.gridy = y;
117        c.anchor = GridBagConstraints.CENTER;
118        c.gridwidth = GridBagConstraints.REMAINDER;
119        _uploadToSeedMe = new JCheckBox("Upload to SeedMe");
120        _uploadToSeedMe.setSelected(true);
121        p.add(_uploadToSeedMe, c);
122
123        y++;
124        c.gridx = 0;
125        c.gridy = y;
126        c.anchor = GridBagConstraints.CENTER;
127        c.gridwidth = GridBagConstraints.REMAINDER;
128        _uploadToDropbox = new JCheckBox("Upload to Dropbox");
129        _uploadToDropbox.setSelected(true);
130        p.add(_uploadToDropbox, c);
131        
132        y++;
133        c.gridx = 0;
134        c.gridy = y;
135        c.anchor = GridBagConstraints.WEST;
136        c.gridwidth = 1;
137        p.add(new JLabel("Title"), c);
138        
139        c.gridx = 1;
140        c.anchor = GridBagConstraints.EAST;
141        _title = new JTextField(workflowName, 30);
142        p.add(_title, c);
143        
144        y++;
145        c.gridx = 0;
146        c.gridy = y;
147        c.anchor = GridBagConstraints.WEST;
148        p.add(new JLabel("Description"), c);
149        
150        c.gridx = 1;
151        c.anchor = GridBagConstraints.EAST;
152        _desc = new JTextField("Outputs from workflow " + workflowName, 30);
153        p.add(_desc, c);
154
155        y++;
156        c.gridx = 0;
157        c.gridy = y;
158        c.anchor = GridBagConstraints.WEST;
159        p.add(new JLabel("License"), c);
160
161        c.gridx = 1;
162        c.anchor = GridBagConstraints.EAST;
163        _license = new JTextField(_licenseStr, 30);
164        p.add(_license, c);
165
166        y++;
167        c.gridx = 0;
168        c.gridy = y;
169        c.anchor = GridBagConstraints.WEST;
170        p.add(new JLabel("Credits"), c);
171        
172        c.gridx = 1;
173        c.anchor = GridBagConstraints.EAST;
174        _credits = new JTextField(_userName, 30);
175        p.add(_credits, c);
176        
177        y++;
178        c.gridx = 0;
179        c.gridy = y;
180        c.anchor = GridBagConstraints.WEST;
181        p.add(new JLabel("Privacy"), c);
182        
183        c.gridx = 1;       
184        c.anchor = GridBagConstraints.WEST;
185        JPanel privacyPanel = new JPanel();
186        p.add(privacyPanel, c);
187        
188        ButtonGroup _privacyButtonGroup = new ButtonGroup();
189        _privacyPrivateButton = new JRadioButton("Private");
190        _privacyPrivateButton.setActionCommand("privacyPrivate");
191        _privacyPrivateButton.addActionListener(this);
192        
193        _privacyGroupButton = new JRadioButton("Group");
194        _privacyGroupButton.setActionCommand("privacyGroup");
195        _privacyGroupButton.addActionListener(this);
196
197        _privacyPublicButton = new JRadioButton("Public");
198        _privacyPublicButton.setActionCommand("privacyPublic");
199        _privacyPublicButton.addActionListener(this);
200
201        _privacyButtonGroup.add(_privacyPrivateButton);
202        privacyPanel.add(_privacyPrivateButton);        
203        _privacyButtonGroup.add(_privacyGroupButton);
204        privacyPanel.add(_privacyGroupButton);
205        _privacyButtonGroup.add(_privacyPublicButton);
206        privacyPanel.add(_privacyPublicButton);
207        
208        // set default to private
209        _privacyPrivateButton.setSelected(true);
210        
211        y++;
212        c.gridx = 0;
213        c.gridy = y;
214        c.anchor = GridBagConstraints.WEST;
215        p.add(new JLabel("Share With"), c);
216        
217        c.gridx = 1;
218        c.anchor = GridBagConstraints.EAST;
219        _shareWith = new JTextField("Enter email addresses", 30);
220        p.add(_shareWith, c);
221        _shareWith.setEnabled(false);
222
223        y++;
224        c.gridx = 0;
225        c.gridy = y;
226        c.anchor = GridBagConstraints.CENTER;
227        c.gridwidth = GridBagConstraints.REMAINDER;
228        JPanel buttonPanel = new JPanel();
229        p.add(buttonPanel, c);
230        
231        JButton _uploadButton = new JButton();
232        _uploadButton.setActionCommand("upload");
233        _uploadButton.addActionListener(this);
234        _uploadButton.setText("Upload");
235        buttonPanel.add(_uploadButton);
236
237        JButton _cancelButton = new JButton(new AbstractAction() {
238            @Override
239            public void actionPerformed(ActionEvent e) {
240                dispose();
241            }
242        });
243        _cancelButton.setText("Cancel");
244        buttonPanel.add(_cancelButton);
245    }
246    
247    @Override
248    public void actionPerformed(ActionEvent event) {
249        
250        String actionStr = event.getActionCommand();
251        
252        if(actionStr.equals("upload")) {
253            
254            // make sure we're uploading to at least one cloud service
255            if(!_uploadToSeedMe.isSelected() && !_uploadToDropbox.isSelected()) {
256                try {
257                    MessageHandler.warning("SeedMe and Dropbox not selected so results not uploaded.");
258                } catch (CancelException e) {
259                    // TODO Auto-generated catch block
260                    e.printStackTrace();
261                }
262                return;
263            }
264            
265            // save workflow if modified
266            if(_parent.isModified()) {
267                SaveArchiveAction saveArchive = new SaveArchiveAction(_parent);
268                saveArchive.actionPerformed(event);
269            }
270
271            WorkflowResults results = null;
272            try {
273                
274                results = _collectResults(); 
275                    
276                //JDialog statusDialog = null;
277                
278                if(_uploadToSeedMe.isSelected()) {
279                    //statusDialog = _createStatusDialog("Uploading to SeedMe.");
280                    try {
281                        final SeedMe seedMeClient = new SeedMe();
282                        String authFileStr = System.getProperty("user.home") +
283                                File.separator +
284                                ".seedme.txt";
285                        String message = seedMeClient.set_auth_via_file(authFileStr);                        
286                        
287                        if(!message.equals("Success")) {
288                            MessageHandler.error("Error reading SeedMe authentication file in\n" +
289                                authFileStr + ": " + message);
290                            return;
291                        }
292                        
293                        HashSet<FileEntry> files = new HashSet<FileEntry>(results.files());
294                                                
295                        String collectionIdStr = seedMeClient.create_collection(
296                            results.getPrivacy().name().toLowerCase(),
297                            results.getShareWith(),
298                            false, // notify
299                            results.get("title"),
300                            results.get("description"),
301                            results.get("credits"),
302                            results.get("license"),
303                            false, // overwrite
304                            new HashMap<String,String>(results.keyValues()),
305                            null, // tags TODO
306                            null, // tickers
307                            files,
308                            null, //sequences,
309                            null); // transfer
310
311                            // open the collection the browser
312
313                            int id = Integer.parseInt(collectionIdStr);                            
314                            String collectionUrlStr = seedMeClient.getCollectionUrl(id);
315                            BrowserLauncher.openURL(collectionUrlStr);
316                            
317                    } catch (Exception e) {
318                        //if(statusDialog != null) {
319                            //statusDialog.dispose();
320                            //statusDialog = null;
321                        //}
322                        MessageHandler.error("Error uploading to SeedMe.", e);
323                    }
324                }
325                //if(statusDialog != null) {
326                    //statusDialog.dispose();
327                    //statusDialog = null;
328                //}
329
330                
331                if(_uploadToDropbox.isSelected()) {
332                    //statusDialog = _createStatusDialog("Uploading to Dropbox.");
333                    try {
334                        
335                        final FileSystemClient fsClient = new FileSystemClient();
336                        fsClient.setResults(results);
337                        fsClient.update();
338                        
339                        BrowserLauncher.openURL(fsClient.getURL().toString());               
340                        
341                    } catch(Exception e) {
342                        //if(statusDialog != null) {
343                            //statusDialog.dispose();
344                            //statusDialog = null;
345                        //}
346
347                        MessageHandler.error("Error uploading to Dropbox.", e);
348                    }
349                }
350                //if(statusDialog != null) {
351                    //statusDialog.dispose();
352                    //statusDialog = null;
353                //}
354
355                
356            } finally {
357                if(results != null) {
358                    results.cleanUp();
359                }
360            }
361            
362            // close the dialog
363            dispose();
364                        
365        } else if(actionStr.equals("privacyPrivate") ||
366                actionStr.equals("privacyPublic")) {
367            _shareWith.setEnabled(false);            
368        } else if(actionStr.equals("privacyGroup")) {
369            _shareWith.setEnabled(true);
370        }
371    }
372    
373    /*
374    private JDialog _createStatusDialog(String message) {
375        
376        final JOptionPane optionPane = new JOptionPane(message, JOptionPane.INFORMATION_MESSAGE,
377                JOptionPane.DEFAULT_OPTION, null, new Object[]{}, null);
378
379        final JDialog dialog = new JDialog();
380        dialog.setTitle(message);
381        dialog.setModal(true);
382
383        dialog.setContentPane(optionPane);
384
385        dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
386        dialog.pack();
387        dialog.setLocationRelativeTo(null);
388        dialog.setVisible(true);
389        return dialog;
390    }
391    */
392    
393    private WorkflowResults _collectResults() {
394        
395        WorkflowResults results = new WorkflowResults();
396        
397        _userName = _credits.getText();
398        _licenseStr = _license.getText();
399        
400        results.setKeyValue("title", _title.getText());
401        results.setKeyValue("description", _desc.getText());
402        results.setKeyValue("credits", _userName);
403        results.setKeyValue("license", _licenseStr);
404        
405        if(_privacyPrivateButton.isSelected()) {
406            results.setPrivacy(WorkflowResults.Privacy.PRIVATE);
407        } else if(_privacyGroupButton.isSelected()) {
408            results.setPrivacy(WorkflowResults.Privacy.GROUP);
409            results.shareWith(_shareWith.getText());                    
410        } else { // _privacyPublicButton.isSelected())
411            results.setPrivacy(WorkflowResults.Privacy.PUBLIC);
412        }
413        
414        final NamedObj top = _parent.getModel().toplevel();                    
415        
416        // add workflow lsid
417        KeplerLSID lsid = NamedObjId.getIdFor(top);
418        results.setKeyValue("workflow_lsid", lsid.toString());
419        
420        // add last execution lsid
421        try {
422            Queryable queryable = ProvenanceRecorder.getDefaultQueryable(top);
423            KeplerLSID run_lsid = queryable.getLastExecutionLSIDForWorkflow(lsid);
424            results.setKeyValue("workflow_run_lsid", run_lsid.toString());
425        } catch(Exception e) {
426            MessageHandler.error("Error querying workflow run LSID", e);
427        }
428    
429        // add metadata values for parameters
430        for(Parameter parameter : top.attributeList(Parameter.class)) {
431            String name = parameter.getName();
432            //System.out.println(name);
433            if(!name.startsWith("_")) {
434                String value;
435                Token token;
436                try {
437                    token = parameter.getToken();
438                    if(token instanceof StringToken) {
439                        value = ((StringToken)token).stringValue();
440                    } else {
441                        value = token.toString();
442                    }
443                    results.setKeyValue(name, value);
444                } catch (IllegalActionException e1) {
445                    // TODO Auto-generated catch block
446                    e1.printStackTrace();
447                }
448            }
449        }
450        
451        // create files for workflow, workflow screenshot,
452        // plotting and display actors, and report pdf
453        File outputDir = Files.createTempDir();
454        results.setTempDir(outputDir);                
455        
456        // add workflow KAR or XML
457        KARFile karFile = KARManager.getInstance().get(_parent);
458        if(karFile != null) {
459            results.addFile(karFile.getFileLocation(), "Workflow KAR",
460                    "Workflow KAR containing the workflow.");
461        } else {
462            // upload XML
463            //MessageHandler.error("Could not find KAR file, so uploading XML workflow instead.");
464        
465            FileWriter writer = null;
466            try {
467                File workflowXMLFile = new File(outputDir, "Workflow.xml");
468                writer = new FileWriter(workflowXMLFile);
469                top.exportMoML(writer);
470                results.addFile(workflowXMLFile, "Worklflow XML", "Workflow XML");
471            } catch(IOException e1) {
472                MessageHandler.error("Error writing workflow XML.", e1);
473            } finally {
474                if(writer != null) {
475                    try {
476                        writer.close();
477                    } catch (IOException e) {
478                        // TODO Auto-generated catch block
479                        e.printStackTrace();
480                    }
481                }
482            }
483        }
484        
485        OutputStream stream = null;
486        try {
487            // add the workflow screen shot
488            File workflowImageFile = new File(outputDir, "Workflow.png");
489            stream = new FileOutputStream(workflowImageFile);
490            _parent.writeImage(stream, "png");
491            results.addFile(workflowImageFile, "Workflow Screenshot", "Workflow Screenshot");
492        } catch (Exception e) {
493            // TODO
494            e.printStackTrace();
495        } finally {
496            if(stream != null) {
497                try {
498                    stream.close();
499                } catch (IOException e) {
500                    // TODO Auto-generated catch block
501                    e.printStackTrace();
502                }
503            }
504        }
505
506        ModelDirectory directory = _parent.getDirectory();
507        _writeEffigies(results, outputDir, directory.entityList(Effigy.class));
508        
509        _collectFilesInShareActor(results, (CompositeEntity) top);
510        
511        // add report pdf if one was generated
512        try {
513            File reportFile = new File(outputDir, "WorkflowReport.pdf");
514            if(_saveReportPDF(reportFile, top)) {
515                results.addFile(reportFile, "Workflow Report", "Workflow Report");
516            }
517        } catch(Exception e) {
518            MessageHandler.error("Error saving report PDF.", e);
519        }
520       
521        return results;
522    }
523    
524    /** Add files collected in any ShareToCloud actors in the workflow. */
525    private void _collectFilesInShareActor(WorkflowResults results, CompositeEntity composite) {
526        
527        for(ShareToCloud actor : composite.entityList(ShareToCloud.class)) {
528            String actorName = actor.getFullName();
529            for(String fileName : actor.getFiles()) {
530                File file = new File(fileName);
531                if(!file.exists()) {
532                    System.err.println("File collected by actor " + actorName + " does not exist.");
533                } else {
534                    results.addFile(file, "", "");
535                }
536            }
537        }
538
539        // recursively add files in sub-workflows.
540        for(CompositeEntity containedComposite : composite.entityList(CompositeEntity.class)) {
541            _collectFilesInShareActor(results, containedComposite);
542        }
543        
544    }
545    
546    /** Save the report of the last execution of the workflow to the specified file. */
547    private boolean _saveReportPDF(File reportFile, NamedObj workflow) throws Exception {
548                
549        // look up the the workflow LSID
550        KeplerLSID lsid = NamedObjId.getIdFor(workflow);
551        Queryable queryable = null;
552        
553        
554        try {
555            // get the latest execution 
556            queryable = ProvenanceRecorder.getDefaultQueryable(workflow);
557            Integer execId = queryable.getLastExecutionForWorkflow(lsid);
558            
559            if(execId != null) {
560                // look up the RIO instance XML in provenance
561                Map<String, String> metadataMap = new HashMap<String, String>();
562                metadataMap.put("type", ReportInstance.class.getName());
563                List<byte[]> dataList = queryable.getAssociatedDataForExecution(execId.intValue(), metadataMap, false);
564                if (dataList != null && !dataList.isEmpty()) {
565                    ByteArrayInputStream stream = null;
566                    try {
567                        stream = new ByteArrayInputStream(dataList.get(0));
568                        ReportInstance report = (ReportInstance)
569                            ReportRenderer.convertXML2Report(stream);
570                        ReportRenderer.convertReport2PDF(report, reportFile, null);
571                        return true;
572                    } finally {
573                        if(stream != null) {
574                            stream.close();
575                        }
576                    }
577                }
578            }
579        } finally {
580            /* XXX disconnecting here closes db connect for provenance recorder
581            if(queryable != null) {
582                queryable.disconnect();
583            }
584            */
585        }
586        
587        return false;
588    }
589    
590    /** Write the contents of open effigies to files. */
591    private void _writeEffigies(WorkflowResults results, File outputDir, List<Effigy> effigies) {
592        
593        for(Effigy effigy : effigies) {
594            for(Tableau tableau : effigy.entityList(Tableau.class)) {
595                Frame frame = tableau.getFrame();
596
597                String title = frame.getTitle().replaceAll(".*\\.([^\\.]+)", "$1");
598                String fileName = title.replaceAll("\\s+", "_");
599                
600
601                OutputStream stream = null;
602                DataOutputStream dataStream = null;
603                File file = null;
604                try {
605                    if(frame instanceof PlotTableauFrame) {
606                        file = new File(outputDir, fileName + ".png");
607                        stream = new FileOutputStream(file);
608                        ((ImageExportable) frame).writeImage(stream, "png");
609                        
610                        NamedObj actor = ((PtolemyEffigy)effigy).getModel();
611                        String className = actor.getClassName();
612                        if(className.lastIndexOf(".") > 0) {
613                            className.substring(className.lastIndexOf(".") + 1);
614                        }
615
616                        results.addFile(file, title,
617                            "Plot from " + className + " actor " + actor.getName());
618                    } else if(frame instanceof TextEditor) {
619                        file = new File(outputDir, fileName + ".txt");
620                        stream = new FileOutputStream(file);
621                        dataStream = new DataOutputStream(stream);
622                        dataStream.writeChars(((TextEditor)frame).text.getText());
623                        // TODO
624                        results.addFile(file, title, "Output from Display actor");// +
625                                //className + " actor " + actor.getName());
626                    }
627                } catch (Exception e) {
628                    e.printStackTrace();
629                } finally {
630                    if(dataStream != null) {
631                        try {
632                            dataStream.close();
633                        } catch (IOException e) {
634                            // TODO Auto-generated catch block
635                            e.printStackTrace();
636                        }
637                    }
638                    if(stream != null) {
639                        try {
640                            stream.close();
641                        } catch (IOException e) {
642                            // TODO Auto-generated catch block
643                            e.printStackTrace();
644                        }
645                    }
646                }
647                // recursively write effigies
648                _writeEffigies(results, outputDir, effigy.entityList(Effigy.class));
649            }
650        }
651    }
652
653    private JCheckBox _uploadToSeedMe;
654    private JCheckBox _uploadToDropbox;
655    
656    private JTextField _title;
657    private JTextField _desc;
658    private JTextField _license;
659    private JTextField _credits;
660    private JTextField _shareWith;
661    
662    private final JRadioButton _privacyPrivateButton;
663    private final JRadioButton _privacyGroupButton;
664    private final JRadioButton _privacyPublicButton;
665    
666    private String _userName = System.getProperty("user.name");
667    private String _licenseStr = "CC BY 3.0: " + _userName;
668    
669    private KeplerGraphFrame _parent;
670
671}