001/* 002 * Copyright (c) 2004-2011 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2014-08-22 18:21:57 +0000 (Fri, 22 Aug 2014) $' 007 * '$Revision: 32916 $' 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.gui.kar; 031 032import java.awt.Color; 033import java.awt.event.ActionEvent; 034import java.io.File; 035import java.io.IOException; 036 037import javax.swing.JFileChooser; 038import javax.swing.JOptionPane; 039 040import org.apache.commons.logging.Log; 041import org.apache.commons.logging.LogFactory; 042import org.kepler.gui.KeplerGraphFrame; 043import org.kepler.gui.ModelToFrameManager; 044import org.kepler.kar.KARFile; 045import org.kepler.kar.KARManager; 046import org.kepler.kar.SaveKAR; 047import org.kepler.objectmanager.cache.LocalRepositoryManager; 048import org.kepler.objectmanager.library.LibraryManager; 049import org.kepler.objectmanager.lsid.KeplerLSID; 050import org.kepler.objectmanager.repository.Repository; 051import org.kepler.objectmanager.repository.RepositoryManager; 052import org.kepler.sms.SMSServices; 053import org.kepler.sms.gui.SemanticTypeEditor; 054import org.kepler.util.DotKeplerManager; 055import org.kepler.util.FileUtil; 056import org.kepler.util.RenameUtil; 057 058import ptolemy.actor.gui.Effigy; 059import ptolemy.actor.gui.PtolemyEffigy; 060import ptolemy.actor.gui.PtolemyFrame; 061import ptolemy.actor.gui.Tableau; 062import ptolemy.actor.gui.TableauFrame; 063import ptolemy.gui.ExtensionFilenameFilter; 064import ptolemy.gui.JFileChooserBugFix; 065import ptolemy.gui.PtFileChooser; 066import ptolemy.gui.PtGUIUtilities; 067import ptolemy.kernel.ComponentEntity; 068import ptolemy.kernel.Entity; 069import ptolemy.kernel.util.IllegalActionException; 070import ptolemy.kernel.util.NamedObj; 071import ptolemy.util.MessageHandler; 072import ptolemy.vergil.basic.BasicGraphFrame; 073import ptolemy.vergil.toolbox.FigureAction; 074 075/** 076 * This action exports a workflow as a kar file. Subclasses of this class should 077 * be used for saving any type of KAR file. To make a subclass you will want to 078 * override the initialize method and the handle action method. See 079 * ExportActorArchiveAction for an example subclass. 080 * 081 * @author Aaron Schultz, Christopher Brooks 082 */ 083public class ExportArchiveAction extends FigureAction { 084 085 private static final Log log = LogFactory.getLog(ExportArchiveAction.class); 086 private static final boolean isDebugging = log.isDebugEnabled(); 087 088 // "A" should probably be used for a too-be-implemeted Edit Select All. 089 //private static KeyStroke ACCELERATOR_KEYSTROKE = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_A, 090 // Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); 091 092 protected TableauFrame _parent; 093 094 protected boolean _saveSucceeded = false; 095 096 /** 097 * The SaveKAR object is a GUI free helper class for saving KARs. 098 */ 099 protected SaveKAR _savekar = null; 100 101 /** 102 * A string that represents to the user the type of KAR that is being 103 * exported. This string may appear in dialog messages to allow different 104 * callers to better identify to the user what the contents of this KAR file 105 * are. This should be the first thing set in the handleType method of any 106 * subclasses of this class but is not required. 107 */ 108 protected String _karType = ""; 109 110 protected boolean refreshFrameAfterSave = true; 111 112 protected boolean _mapKARToCurrentFrame = true; 113 114 /** 115 * The purpose of KARs are for grouping things together. Sometimes though we 116 * have only one thing in a KAR which changes some things. This is a bit 117 * confusing here because this refers to a single item being saved from the 118 * user perspective. Just because we're saving a single item at this stage 119 * does not mean there will only be one file in the KAR. There may be other 120 * files associated with the single item that we're saving from this class. 121 * For example we may be saving a single workflow that has many files 122 * associated with it. 123 */ 124 protected boolean _singleItemKAR = false; 125 126 protected String _defaultFileName = null; 127 128 protected String _overrideModuleDependencies = null; 129 130 protected boolean _upload = true; 131 132 protected File _saveFile = null; 133 134 protected boolean _nonInteractiveSave = false; 135 136 /** The workflow associated with the parent frame. */ 137 private ComponentEntity<?> _workflow = null; 138 139 /** 140 * Call this before actionPerformed to do a 141 * non-interactive Save, not a Save As... 142 * and don't attempt upload. 143 * @param saveFile 144 */ 145 public void setSaveFile(File saveFile){ 146 _saveFile = saveFile; 147 _nonInteractiveSave = true; 148 setUpload(false); 149 } 150 151 public boolean isSingleItemKAR() { 152 return _singleItemKAR; 153 } 154 155 public void setSingleItemKAR(boolean singleItemKAR) { 156 _singleItemKAR = singleItemKAR; 157 } 158 159 /** 160 * @return boolean controlling if upload should 161 * occur 162 */ 163 public boolean doUpload(){ 164 return _upload; 165 } 166 167 /** 168 * Set false if you don't want user prompted to upload 169 * after save if they have a remote repository selected. 170 * @param upload 171 */ 172 public void setUpload(boolean upload){ 173 _upload = upload; 174 } 175 176 public SaveKAR getSaveKAR() { 177 return _savekar; 178 } 179 180 /** 181 * Set default KAR save filename. 182 * 183 * @param name 184 */ 185 public void setDefaultFileName(String name) { 186 _defaultFileName = name; 187 } 188 189 /** 190 * Get default KAR save filename. 191 * 192 * @return _defaultFileName 193 */ 194 public String getDefaultFileName() { 195 return _defaultFileName; 196 } 197 198 /** 199 * @return the refreshFrameAfterSave 200 */ 201 public boolean isRefreshFrameAfterSave() { 202 return refreshFrameAfterSave; 203 } 204 205 /** 206 * Allow for toggling the close/open of the main frame after saving. This 207 * makes sense for workflows but not for other types of saves like actors. 208 * 209 * @param refreshFrameAfterSave 210 * the refreshFrameAfterSave to set 211 */ 212 public void setRefreshFrameAfterSave(boolean refreshFrameAfterSave) { 213 this.refreshFrameAfterSave = refreshFrameAfterSave; 214 } 215 216 /** 217 * Set true when this KAR should not be mapped to this JFrame. 218 */ 219 public void setMapKARToCurrentFrame(boolean mapKARToCurrentFrame){ 220 _mapKARToCurrentFrame = mapKARToCurrentFrame; 221 } 222 public boolean mapKARToCurrentFrame(){ 223 return _mapKARToCurrentFrame; 224 } 225 226 /** 227 * Convenience reference to the LocalRepositoryManager. 228 */ 229 protected LocalRepositoryManager _localRepositoryManager; 230 231 /** 232 * prevent the file chooser from displaying and just use a temp file 233 */ 234 public boolean useTempFile = false; 235 236 /** 237 * Constructor 238 * 239 * @param parent 240 * the "frame" (derived from ptolemy.gui.Top) where the menu is 241 * being added. 242 */ 243 public ExportArchiveAction(TableauFrame parent) { 244 super(""); 245 246 //putValue(ACCELERATOR_KEY, ACCELERATOR_KEYSTROKE); 247 248 _parent = parent; 249 250 if (parent == null) { 251 IllegalArgumentException iae = new IllegalArgumentException( 252 "ExportArchiveAction constructor received NULL argument for TableauFrame"); 253 iae.fillInStackTrace(); 254 throw iae; 255 } 256 257 _localRepositoryManager = LocalRepositoryManager.getInstance(); 258 259 _savekar = new SaveKAR(); 260 261 initialize(); 262 } 263 264 /** 265 * The initialize method is called at the end of the public constructor. 266 * This makes it easier to change the behavior of constructing a subclass. 267 */ 268 protected void initialize() { 269 270 _karType = "workflow"; 271 272 this.setSingleItemKAR(true); 273 274 this.putValue("tooltip", "Save a KAR file archive."); 275 276 } 277 278 /** 279 * Invoked when an action occurs. 280 * 281 * @param e 282 * ActionEvent 283 */ 284 @Override 285 public void actionPerformed(ActionEvent e) { 286 super.actionPerformed(e); 287 288 _saveSucceeded = false; 289 290 // grab the new KAR lsid after the file is saved 291 // in case we need it for later 292 KeplerLSID theNewKARLSID = null; 293 294 // if the frame is for a sub-workflow, ask the user if they 295 // want to save for the sub-workflow only. 296 if(_parent instanceof PtolemyFrame) { 297 NamedObj workflow = ((PtolemyFrame) _parent).getModel(); 298 NamedObj topWorkflow = workflow.toplevel(); 299 if (workflow != topWorkflow) { 300 if(!MessageHandler.yesNoQuestion("Save sub-workflow only?")) { 301 _parent = ModelToFrameManager.getInstance().getFrame(topWorkflow); 302 } 303 } 304 } 305 306 307 // save the window size, position, and zoom 308 if (_parent instanceof BasicGraphFrame) { 309 try { 310 ((BasicGraphFrame) _parent).updateWindowAttributes(); 311 } catch (Exception exception) { 312 MessageHandler 313 .error("Failed to save window size, position and zoom while writing KAR.", 314 exception); 315 } 316 } 317 318 // //////////////////////////////////////////////// 319 // Only this part should be different depending on 320 // where the KAR is being exported from 321 boolean continueExport = handleAction(e); 322 if (!continueExport) { 323 return; 324 } 325 // //////////////////////////////////////////////// 326 327 // Force single item KAR if there is only one 328 // item in the KAR 329 // This may or may not be good/necessary 330 if (_savekar.getSaveInitiatorList().size() == 1) { 331 setSingleItemKAR(true); 332 } else { 333 setSingleItemKAR(false); 334 } 335 336 File saveFile = null; 337 338 if (_nonInteractiveSave) { 339 saveFile = _saveFile; 340 } else { 341 if (useTempFile) { 342 343 // Use a temporary file for saving to in order to simulate 344 // the old upload to repository function 345 // don't really want to be doing this... 346 ComponentEntity<?> ce = _savekar.getSaveInitiatorList().get(0); 347 String tempFileName = ce.getName() + ".kar"; 348 File tempDir = DotKeplerManager.getInstance() 349 .getTransientModuleDirectory("core"); 350 saveFile = new File(tempDir, tempFileName); 351 352 } else { 353 // Create a file filter that accepts .kar files. 354 ExtensionFilenameFilter filter = new ExtensionFilenameFilter("kar"); 355 // Avoid white boxes in file chooser, see 356 // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3801 357 JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix(); 358 Color background = null; 359 PtFileChooser chooser = null; 360 361 try { 362 background = jFileChooserBugFix.saveBackground(); 363 chooser = new PtFileChooser(_parent, "Save", 364 JFileChooser.SAVE_DIALOG); 365 if(_parent instanceof BasicGraphFrame) { 366 chooser.setCurrentDirectory(((BasicGraphFrame)_parent).getLastDirectory()); 367 } 368 chooser.addChoosableFileFilter(filter); 369 370 if(_defaultFileName != null) { 371 chooser.setSelectedFile(new File(_defaultFileName)); 372 } 373 374 int returnVal = chooser.showDialog(_parent, "Save"); 375 if (returnVal == JFileChooser.APPROVE_OPTION) { 376 // process the given file 377 saveFile = chooser.getSelectedFile(); 378 379 if(saveFile.exists() && !PtGUIUtilities.useFileDialog()) { 380 if (!MessageHandler.yesNoQuestion("Overwrite \"" 381 + saveFile.getName() + "\"?")) { 382 saveFile = null; 383 } 384 } 385 } 386 } finally { 387 jFileChooserBugFix.restoreBackground(background); 388 } 389 390 } 391 } 392 393 if (saveFile == null) 394 return; 395 396 _savekar.setFile(saveFile); 397 398 // see if the name has changed. 399 400 // make sure there's a reference to the workflow; it could be null when, 401 // e.g., exporting a run in the WRM. 402 if(_workflow != null) { 403 String newName = FileUtil.getFileNameWithoutExtension(saveFile.getName()); 404 if(!newName.equals(_workflow.getName())) { 405 try { 406 // rename the frame title and workflow xml file in the kar 407 RenameUtil.renameComponentEntity(_workflow, newName); 408 } catch(Exception exception) { 409 MessageHandler.error("Error renaming workflow.", exception); 410 } 411 } 412 } 413 414 // See if this file already exists 415 if (_savekar.getFile().exists()) { 416 try { 417 // Get the LSID of the existing kar 418 KARFile existingKARFile = new KARFile(_savekar.getFile()); 419 KeplerLSID existingFileLSID = existingKARFile.getLSID(); 420 // Delete the old kar from the library 421 LibraryManager lm = LibraryManager.getInstance(); 422 lm.deleteKAR(_savekar.getFile()); 423 // increment the revision and set it 424 existingFileLSID.incrementRevision(); 425 _savekar.specifyLSID(existingFileLSID); 426 // save the new kar file 427 theNewKARLSID = _savekar.saveToDisk(_parent, _overrideModuleDependencies); 428 } catch (Exception e1) { 429 e1.printStackTrace(); 430 } 431 } else { 432 theNewKARLSID = _savekar.saveToDisk(_parent, _overrideModuleDependencies); 433 } 434 435 if (theNewKARLSID != null) { 436 _saveSucceeded = true; 437 438 // Add JFrame=>KARFile mapping to KARManager, unless ifRefreshFrameAfterSave, 439 // in which case the mapping is added during ActorMetadataKAREntry.open, or if 440 // mapKARToCurrentFrame was explicitly set false. 441 if (!isRefreshFrameAfterSave() && mapKARToCurrentFrame()){ 442 try { 443 KARFile karFile = new KARFile(_savekar.getFile()); 444 KARManager.getInstance().add(_parent, karFile); 445 } catch (IOException e1) { 446 // TODO Auto-generated catch block 447 e1.printStackTrace(); 448 } 449 } 450 451 if(_parent instanceof KeplerGraphFrame) { 452 try { 453 ((KeplerGraphFrame) _parent).updateHistory(_savekar.getFile().getAbsolutePath()); 454 } catch (IOException e1) { 455 MessageHandler.error("Unable to update history menu.", e1); 456 } 457 } 458 if(_parent instanceof BasicGraphFrame) { 459 ((BasicGraphFrame)_parent).setLastDirectory(saveFile.getParentFile()); 460 } 461 } 462 463 // After the KAR has been saved to disk we add it to the cache 464 // then add it to the library and refresh the JTrees 465 if (!useTempFile) { 466 467 // If it is saved in a local repository update the library 468 LocalRepositoryManager lrm = LocalRepositoryManager.getInstance(); 469 if (lrm.isInLocalRepository(_savekar.getFile())) { 470 471 _savekar.saveToCache(); 472 473 LibraryManager lm = LibraryManager.getInstance(); 474 try { 475 lm.addKAR(_savekar.getFile()); 476 } catch (Exception e2) { 477 JOptionPane.showMessageDialog(_parent, 478 "Error adding kar to library: " + e2.getMessage()); 479 } 480 try { 481 lm.refreshJTrees(); 482 483 } catch (IllegalActionException e2) { 484 e2.printStackTrace(); 485 } 486 } else { 487 // JOptionPane.showMessageDialog(_parent, 488 // "KAR file successfully saved to a folder that is not designated as a Local Repository. In order for the contents of this KAR to show up in the component library, you can move the KAR file to a Local Repository and restart Kepler, or you can add the folder as a local repository by using the Component 'Sources' button."); 489 } 490 } 491 492 // Now we 493 // 1. check to see if there is a remote repository selected by the user 494 // for saving 495 // 2. double check with the user that they want to send the KAR 496 // to the remote repository, 497 // 3. then upload it 498 try { 499 if (_upload) { 500 RepositoryManager rm = RepositoryManager.getInstance(); 501 Repository remoteRepo = rm.getSaveRepository(); 502 if (remoteRepo != null) { 503 boolean continueWithUpload = true; 504 int choice = JOptionPane.showConfirmDialog(_parent, 505 "Would you like to upload this KAR to the remote repository?\n" 506 + " " + remoteRepo.getName() + " at " 507 + remoteRepo.getRepository() + "\n", 508 "Upload To Repository", JOptionPane.YES_NO_OPTION); 509 if (choice != JOptionPane.YES_OPTION) { 510 continueWithUpload = false; 511 } 512 513 if (continueWithUpload) { 514 515 try { 516 ComponentUploader uploader = new ComponentUploader( 517 _parent); 518 KARFile kf2upload = new KARFile(getSaveKAR() 519 .getFile()); 520 uploader.upload(kf2upload); 521 } catch (Exception ee) { 522 ee.printStackTrace(); 523 } 524 525 } 526 527 } 528 } 529 530 if (isRefreshFrameAfterSave()) { 531 // Open a new frame and close the old one 532 KARFile karf; 533 try { 534 535 //move old frame close before new frame open. 536 //It will fix the bug http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5200 without having memory leak. 537 karf = new KARFile(_savekar.getFile()); 538 karf.openKARContents(_parent, false); 539 karf.close(); 540 541 // dispose the old window after opening a new one 542 _parent.dispose(); 543 544 } catch (Exception e1) { 545 e1.printStackTrace(); 546 } 547 } 548 } catch (IOException e1) { 549 e1.printStackTrace(); 550 } catch (Exception e1) { 551 e1.printStackTrace(); 552 } 553 554 } 555 556 /** 557 * @return whether or not the file was saved to disk 558 */ 559 public boolean saveSucceeded() { 560 return _saveSucceeded; 561 } 562 563 /** 564 * This method will set up the SaveKAR object in the case of saving a 565 * workflow. To save other types of KARs a subclass that overrides this 566 * method can be used to add multiple workflows to the SaveKAR object and to 567 * identify other objects that should be added to the KAR through the 568 * appropriate KAREntryHandlers. 569 * 570 * @param e 571 * @return boolean true if the export should continue 572 */ 573 protected boolean handleAction(ActionEvent e) { 574 575 // get the workflow entity from parent 576 Tableau tableau = _parent.getTableau(); 577 Effigy effigy = (Effigy) tableau.getContainer(); 578 Entity<?> entity = null; 579 if (effigy instanceof PtolemyEffigy) { 580 entity = (Entity<?>) ((PtolemyEffigy) effigy).getModel(); 581 } 582 583 if (entity == null){ 584 return false; 585 } 586 587 if (!checkSingleObject(entity, false)) { 588 return false; 589 } 590 591 _workflow = (ComponentEntity<?>) entity; 592 593 // When it's ready to go add it and continue 594 _savekar.addSaveInitiator((ComponentEntity<?>) entity); 595 return true; 596 } 597 598 protected void overrideModuleDependencies(String moduleDependencies) { 599 _overrideModuleDependencies = moduleDependencies; 600 } 601 602 /** 603 * Check a single NamedObj for LSID, name, and SemanticType. 604 * 605 * @param checkIfSemenaticallyAnnotated If is true, and entity has no 606 * semantic annotations, user is warned, but not required, to add 607 * annotations. 608 * 609 * @return Returns true if the export should continue 610 */ 611 protected boolean checkSingleObject(NamedObj entity, 612 boolean checkIfSemenaticallyAnnotated) { 613 614 // Make sure it has an LSID 615 _savekar.checkNamedObjLSID(entity); 616 617 if (entity instanceof ComponentEntity) { 618 // Make sure it has a Name 619 // Query the user for a name if needed 620 try { 621 _savekar.checkNamedObjName(entity); 622 623 // FIXME: shouldn't really need this 624 // LSID should be the unique identifier for the library 625 /* 626 * LibraryManager lm = LibraryManager.getInstance(); if (lm == 627 * null || lm.componentNameInUse(entity.getName())) { throw new 628 * NameDuplicationException(null, ""); } 629 */ 630 631 } catch (Exception e1) { 632 633 // / TODO 634 // / it might be good to just go through the usual saveAs route 635 // here, but 636 // / it's challenging since the frame closes and a new is opened 637 // without 638 // / returning a ref. Also the KAR system reopens everything 639 // once done 640 // / (which would result in two of the same workflow being 641 // open). 642 // /kgf = (KeplerGraphFrame) this._parent; 643 // /kgf._saveAs(".xml"); 644 645 // TODO very similar to code in RenameComponentEntityAction, 646 // find a way to merge? 647 String message = "Please enter a name"; 648 if (!_karType.equals("")) { 649 message += " for this " + _karType; 650 } 651 message += ": "; 652 653 String warnMessage = "ERROR name cannot contain the < sign"; 654 655 String newName = JOptionPane.showInputDialog(message, 656 entity.getName()); 657 if (newName == null) { 658 // user hit the cancel button 659 return false; 660 } 661 662 int lessThan = newName.indexOf("<"); 663 if (lessThan >= 0) { 664 JOptionPane.showMessageDialog(_parent, warnMessage, 665 "Error", JOptionPane.ERROR_MESSAGE); 666 return false; 667 } 668 669 try { 670 671 RenameUtil.renameComponentEntity((ComponentEntity<?>) entity, 672 newName); 673 _parent.setTitle(entity.getName()); 674 setDefaultFileName(newName + ".kar"); 675 676 } catch (Exception e) { 677 e.printStackTrace(); 678 JOptionPane 679 .showMessageDialog(_parent, "A problem occured."); 680 return false; 681 } 682 683 try { 684 _savekar.checkNamedObjName(entity); 685 } catch (Exception e2) { 686 if (e2.getMessage().equals("Unnamed")) { 687 // continue using the unnamed name 688 } else { 689 JOptionPane.showMessageDialog(_parent, 690 "KAR contents must have a name."); 691 // stop the export 692 return false; 693 } 694 } 695 } 696 } 697 698 // Check to see if it has at least one SemanticType 699 // Ask the user if they want to add one if it doesn't 700 if (checkIfSemenaticallyAnnotated 701 && SMSServices.getActorSemanticTypes(entity).size() == 0) { 702 703 String message = "In order for KAR item: " + entity.getName() 704 + " to show up in an Ontology" 705 + " it must contain at least one Semantic Type.\n" 706 + " Would you like to add a Semantic Type? "; 707 String title = "Add Semantic Types?"; 708 int choice = JOptionPane.showConfirmDialog(_parent, message, title, 709 JOptionPane.YES_NO_OPTION); 710 if (choice == JOptionPane.YES_OPTION) { 711 712 SemanticTypeEditor editor = new SemanticTypeEditor(_parent, 713 entity); 714 editor.setModal(true); 715 editor.setVisible(true); 716 } 717 718 } 719 720 return true; 721 722 } 723}