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}