001/* 002 * Copyright (c) 2004-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2015-08-24 22:47:39 +0000 (Mon, 24 Aug 2015) $' 007 * '$Revision: 33633 $' 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.sdm.spa; 031 032import java.awt.Container; 033import java.awt.GridBagConstraints; 034import java.awt.GridBagLayout; 035import java.awt.Insets; 036import java.io.ByteArrayOutputStream; 037import java.util.HashSet; 038import java.util.Hashtable; 039 040import javax.swing.JLabel; 041import javax.swing.JOptionPane; 042import javax.swing.JPanel; 043import javax.swing.JPasswordField; 044import javax.swing.JTextField; 045 046import com.jcraft.jsch.ChannelExec; 047import com.jcraft.jsch.JSch; 048import com.jcraft.jsch.Session; 049import com.jcraft.jsch.UIKeyboardInteractive; 050import com.jcraft.jsch.UserInfo; 051 052import ptolemy.actor.TypedAtomicActor; 053import ptolemy.actor.TypedIOPort; 054import ptolemy.actor.parameters.PortParameter; 055import ptolemy.data.BooleanToken; 056import ptolemy.data.IntToken; 057import ptolemy.data.StringToken; 058import ptolemy.data.Token; 059import ptolemy.data.expr.FileParameter; 060import ptolemy.data.expr.Parameter; 061import ptolemy.data.type.BaseType; 062import ptolemy.kernel.CompositeEntity; 063import ptolemy.kernel.util.Attribute; 064import ptolemy.kernel.util.IllegalActionException; 065import ptolemy.kernel.util.NameDuplicationException; 066 067////////////////////////////////////////////////////////////////////////// 068//// Ssh2Exec 069/** 070 * <p> 071 * Connects to a remote host using Ssh2 protocol. 072 * 073 * </p> 074 * <p> 075 * Error conditions this actor must respond robustly to: 076 * <ul> 077 * <li>Wrong identity file given.</li> 078 * <li>Host unreachable.</li> 079 * <li>Login unsuccessful.</li> 080 * <li>Session dies prematurely.</li> 081 * </ul> 082 * 083 * </p> 084 * <p> 085 * This actor will keep the session open until it receives a different username 086 * and host combination. 087 * 088 * </p> 089 * <p> 090 * Modifications: 091 * <ul> 092 * <li>Added support for password authentication</li> 093 * <li>When no identity is specified, the connection will revert to password 094 * authentication.</li> 095 * <li>The actor retains the password information for user@host, so the user 096 * will only be prompted for passwd once only for each user@host. It is 097 * implemented through a static hashtable.</li> 098 * </ul> 099 * </p> 100 * Reference: Ant version 1.6.2. 101 * 102 * @author Ilkay Altintas, Xiaowen Xin 103 * @version $Id: Ssh2Exec.java 33633 2015-08-24 22:47:39Z crawl $ 104 */ 105 106 107/** 108 * 109 * 110 * FIXME 111 * THIS ACTOR SHARES DUPLICATE CODE WITH Ssh2Exec. BEFORE MAKING CHANGES HERE 112 * FACTOR OUT THE DUPLICATED CODE FROM BOTH CLASSES. 113 * 114 * 115 */ 116 117 118public class Ssh2Exec extends TypedAtomicActor { 119 120 /** 121 * Construct an SSH2 actor with the given container and name. Create the 122 * parameters, initialize their values. 123 * 124 * @param container 125 * The container. 126 * @param name 127 * The name of this actor. 128 * @exception IllegalActionException 129 * If the entity cannot be contained by the proposed 130 * container. 131 * @exception NameDuplicationException 132 * If the container already has an actor with this name. 133 */ 134 public Ssh2Exec(CompositeEntity container, String name) 135 throws NameDuplicationException, IllegalActionException { 136 super(container, name); 137 138 // initialize our variables 139 _jsch = new JSch(); 140 _setIdentities = new HashSet(); 141 142 // create all the ports 143 user = new PortParameter(this, "user"); 144 host = new PortParameter(this, "host"); 145 paramIdentity = new FileParameter(this, "identity"); 146 identity = new TypedIOPort(this, "identity", true, false); 147 command = new TypedIOPort(this, "command", true, false); 148 stdout = new TypedIOPort(this, "stdout", false, true); 149 stderr = new TypedIOPort(this, "stderr", false, true); 150 returncode = new TypedIOPort(this, "returncode", false, true); 151 errors = new TypedIOPort(this, "errors", false, true); 152 streamingMode = new Parameter(this, "streaming mode", new BooleanToken( 153 false)); 154 streamingMode.setTypeEquals(BaseType.BOOLEAN); 155 156 // Set the type constraints. 157 user.setTypeEquals(BaseType.STRING); 158 host.setTypeEquals(BaseType.STRING); 159 identity.setTypeEquals(BaseType.STRING); 160 command.setTypeEquals(BaseType.STRING); 161 stdout.setTypeEquals(BaseType.STRING); 162 stderr.setTypeEquals(BaseType.STRING); 163 returncode.setTypeEquals(BaseType.INT); 164 errors.setTypeEquals(BaseType.STRING); 165 166 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" " 167 + "width=\"75\" height=\"50\" style=\"fill:gray\"/>\n" 168 + "<text x=\"5\" y=\"30\"" 169 + "style=\"font-size:25; fill:yellow; font-family:SansSerif\">" 170 + "SSH2</text>\n" + "</svg>\n"); 171 } 172 173 // //////////////// Public ports and parameters /////////////////////// 174 175 /** 176 * Username on the SSH host to be connected to. 177 */ 178 public PortParameter user; 179 /** 180 * Host to connect to. 181 */ 182 public PortParameter host; 183 /** 184 * The file path for <i>userName</i>'s ssh identity file if the user wants 185 * to connect without having to enter the password all the time. 186 * 187 * <p> 188 * The user can browse this file as it is a parameter. 189 * </p> 190 */ 191 public FileParameter paramIdentity; 192 /** 193 * The string representation of the file path for <i>userName</i>'s ssh 194 * identity file if the user wants to connect without having to enter the 195 * password all the time. 196 * 197 * <p> 198 * This is the input option for the identity file. 199 * </p> 200 */ 201 public TypedIOPort identity; 202 /** 203 * The command to be executed on the remote host. 204 * 205 * <p> 206 * It needs to be provided as a string. 207 * </p> 208 */ 209 public TypedIOPort command; 210 211 /** 212 * Output of the command as it would output to the standard shell output. 213 */ 214 public TypedIOPort stdout; 215 /** 216 * The error that were reported by the remote execution or while connecting. 217 */ 218 public TypedIOPort stderr; 219 /** 220 * The return code of the execution. 221 * 222 * <p> 223 * This port will return <i>0 (zero)</i> if the execution is not succesfull, 224 * and a positive integer if it is successful. 225 * </p> 226 */ 227 public TypedIOPort returncode; 228 /** 229 * The string representation of all the errors that happened during the 230 * execution of the actor, if there are any. 231 */ 232 public TypedIOPort errors; 233 234 /** 235 * Specifying whether the output should be sent in a streaming mode. 236 */ 237 public Parameter streamingMode; 238 239 // ///////////////////////////////////////////////////////////////// 240 // // public methods //// 241 242 /** 243 * Callback for changes in attribute values Get the WSDL from the given URL. 244 * 245 * @param at 246 * The attribute that changed. 247 * @exception IllegalActionException 248 */ 249 public void attributeChanged(Attribute at) throws IllegalActionException { 250 /* 251 * if ((at == user) || (at == host)) { if (! 252 * (user.getExpression().equals(""))) { String temp1 = 253 * user.getExpression(); if (! (host.getExpression().equals(""))) { 254 * String temp2 = ( (StringToken) host.getToken()).stringValue(); 255 * _attachText( "_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" " 256 * + "width=\"90\" height=\"60\" style=\"fill:gray\"/>\n" + 257 * "<text x=\"5\" y=\"15\"" + 258 * "style=\"font-size:12; fill:yellow; font-family:SansSerif\">" + temp1 259 * + "\n@\n" + temp2 + "</text>\n" + "</svg>\n"); } else { _attachText( 260 * "_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" " + 261 * "width=\"90\" height=\"60\" style=\"fill:gray\"/>\n" + 262 * "<text x=\"5\" y=\"15\"" + 263 * "style=\"font-size:12; fill:yellow; font-family:SansSerif\">" + temp1 264 * + "\n@\nunknown_host</text>\n" + "</svg>\n"); } } else { if 265 * (host.getExpression().equals("")){ _attachText( "_iconDescription", 266 * "<svg>\n" + "<rect x=\"0\" y=\"0\" " + 267 * "width=\"75\" height=\"50\" style=\"fill:gray\"/>\n" + 268 * "<text x=\"5\" y=\"30\"" + 269 * "style=\"font-size:25; fill:yellow; font-family:SansSerif\">" + 270 * "SSH2</text>\n" + "</svg>\n"); } else { String temp2 = ( 271 * (StringToken) host.getToken()).stringValue(); _attachText( 272 * "_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" " + 273 * "width=\"90\" height=\"60\" style=\"fill:gray\"/>\n" + 274 * "<text x=\"5\" y=\"15\"" + 275 * "style=\"font-size:12; fill:yellow; font-family:SansSerif\">" + 276 * "unknown_user\n@\n" + temp2 + "</text>\n" + "</svg>\n"); } } 277 * 278 * } 279 */ 280 } // end-of-attributeChanged 281 282 /** 283 * Send the token in the <i>value</i> parameter to the output. 284 * 285 * @exception IllegalActionException 286 * If it is thrown by the send() method sending out the 287 * token. 288 */ 289 public void fire() throws IllegalActionException { 290 super.fire(); 291 // by default we will try to connect with private/public keys. 292 boolean isPassAuth = false; 293 294 user.update(); 295 host.update(); 296 297 Token token = user.getToken(); 298 if(token == null) { 299 throw new IllegalActionException(this, "No user specified."); 300 } 301 String strUser = ((StringToken) token).stringValue(); 302 303 token = host.getToken(); 304 if(token == null) { 305 throw new IllegalActionException(this, "No host specified."); 306 } 307 String strHost = ((StringToken) token).stringValue(); 308 309 token = command.get(0); 310 if(token == null) { 311 throw new IllegalActionException(this, "No command specified."); 312 } 313 String strCommand = ((StringToken) token).stringValue(); 314 315 token = streamingMode.getToken(); 316 if(token == null) { 317 throw new IllegalActionException(this, "Stream mode not specified."); 318 } 319 streaming = ((BooleanToken) token).booleanValue(); 320 321 String strIdentity; 322 if (identity.getWidth() > 0) { 323 strIdentity = ((StringToken) identity.get(0)).stringValue(); 324 } else { 325 strIdentity = ((StringToken) paramIdentity.getToken()) 326 .stringValue(); 327 } 328 329 if (strIdentity != null && strIdentity.length() > 0) { 330 // Hack the path because we can't deal with "file:" or "file://" 331 if (strIdentity.startsWith("file:")) { 332 strIdentity = strIdentity.substring(5); 333 334 if (strIdentity.startsWith("//")) { 335 strIdentity = strIdentity.substring(2); 336 } 337 } 338 } else { 339 // We are now need to connect with password 340 isPassAuth = true; 341 } 342 343 try { 344 if (isPassAuth) { 345 // Use password authentication, where use will be prompted 346 // for password. The password should be valid for the session 347 348 _connect(strUser, strHost); 349 } else { 350 _connect(strUser, strHost, strIdentity); 351 } 352 353 _exec(strCommand); 354 } catch (IllegalActionException e) { 355 // caught an exception, so output it to the errors port 356 stdout.send(0, new StringToken("")); 357 stderr.send(0, new StringToken("")); 358 returncode.send(0, new IntToken(0)); 359 errors.send(0, new StringToken(e.getMessage())); 360 throw e; 361 } 362 } 363 364 /** 365 * Terminate any sessions. This method is invoked exactly once per execution 366 * of an application. None of the other action methods should be be invoked 367 * after it. 368 * 369 * @exception IllegalActionException 370 * Not thrown in this base class. 371 */ 372 public void wrapup() throws IllegalActionException { 373 _disconnect(); 374 } 375 376 // //////////////// Private Methods /////////////////////// 377 378 /** 379 * @throws IllegalActionException 380 * If the connection fails. 381 * 382 * 383 * FIXME See FIXME at top of file 384 */ 385 private void _connect(String strUser, String strHost, String strIdentity) 386 throws IllegalActionException { 387 388 _debug("Connecting with " + strUser + "@" + strHost 389 + " with identity: " + strIdentity); 390 391 try { 392 393 strIdentity = strIdentity.trim(); 394 if (!strIdentity.equals("")) { 395 if (_setIdentities.add(strIdentity)) { 396 // we haven't seen this identity before 397 _jsch.addIdentity(strIdentity); 398 } 399 } 400 401 if (!strUser.equals(_strOldUser) || !strHost.equals(_strOldHost) 402 || !_session.isConnected()) { 403 404 if (null != _session && _session.isConnected()) { 405 _disconnect(); 406 } 407 408 _session = _jsch.getSession(strUser, strHost, 22); 409 _strOldUser = strUser; 410 _strOldHost = strHost; 411 412 // username and passphrase will be given via UserInfo interface. 413 UserInfo ui = new MyUserInfo(); 414 _session.setUserInfo(ui); 415 _session.connect(30000); 416 } 417 418 } catch (Exception e) { 419 // a couple of possible exception messages that could happen here: 420 // 1. java.io.FileNotFoundException 421 // 2. session is down 422 System.err.println("Exception caught in " + this.getFullName()); 423 System.err.println("I was trying to connect with " + strUser + "@" 424 + strHost + " with identity: " + strIdentity); 425 e.printStackTrace(); 426 throw new IllegalActionException("Exception caught in " 427 + this.getFullName() + "\n(" + e.getClass().getName() 428 + ")\n" + e.getMessage()); 429 } 430 } 431 432 /** 433 * Connect with password 1. When connected for the first time, it will 434 * prompt for password. 2. When execute for the same user@host, it should 435 * use the stored password. 3. When connect to a different user@host, it can 436 * prompt password again. 437 * 438 * @throws IllegalActionException 439 * If the connection fails. 440 * 441 * FIXME See FIXME at top of file 442 */ 443 private void _connect(String strUser, String strHost) 444 throws IllegalActionException { 445 446 _debug("Connecting with " + strUser + "@" + strHost + " with password."); 447 try { 448 449 if (!strUser.equals(_strOldUser) || !strHost.equals(_strOldHost) 450 || !_session.isConnected()) { 451 452 if (null != _session && _session.isConnected()) { 453 _disconnect(); 454 } 455 456 _session = _jsch.getSession(strUser, strHost, 22); 457 _strOldUser = strUser; 458 _strOldHost = strHost; 459 460 // username and passphrase will be given via UserInfo interface. 461 462 // check whether ui is already set 463 UserInfo ui; 464 ui = (UserInfo) hash.get(strUser + "@" + strHost); 465 if (ui == null) { 466 ui = new MyUserInfo(); 467 } 468 469 // If it is already there. We will use that. 470 // Hopefully we can use the info for connect to the 471 // same user@host 472 473 _session.setUserInfo(ui); 474 _session.connect(); 475 // add to the hashtable 476 hash.put(strUser + "@" + strHost, ui); 477 478 } 479 480 } catch (Exception e) { 481 // a couple of possible exception messages that could happen here: 482 // 1. java.io.FileNotFoundException 483 // 2. session is down 484 System.err.println("Exception caught in " + this.getFullName()); 485 System.err.println("I was trying to connect with " + strUser + "@" 486 + strHost + " with password."); 487 e.printStackTrace(); 488 throw new IllegalActionException("Exception caught in " 489 + this.getFullName() + "\n(" + e.getClass().getName() 490 + ")\n" + e.getMessage()); 491 } 492 } 493 494 /** 495 * 496 * @throws IllegalActionException 497 * if disconnect fails. 498 * 499 * FIXME See FIXME at top of file 500 * 501 */ 502 private void _disconnect() throws IllegalActionException { 503 if (null == _session) { 504 // no session, so nothing to disconnect 505 return; 506 } 507 508 try { 509 _session.disconnect(); 510 } catch (Exception e) { 511 System.err.println("Exception caught in " + this.getFullName()); 512 e.printStackTrace(); 513 throw new IllegalActionException("Exception caught in " 514 + this.getFullName() + "\n(" + e.getClass().getName() 515 + ")\n" + e.getMessage()); 516 } 517 } 518 519 /** 520 * 521 * @throws IllegalActionException 522 */ 523 private void _exec(String execCommand) throws IllegalActionException { 524 if (null == _session) { 525 // no session, so way to execute 526 return; 527 } 528 529 try { 530 final ChannelExec channel = (ChannelExec) _session 531 .openChannel("exec"); 532 channel.setCommand(execCommand); 533 534 streamOut = new ByteArrayOutputStream(); 535 ByteArrayOutputStream streamErr = new ByteArrayOutputStream(); 536 537 channel.setOutputStream(streamOut); 538 channel.setErrStream(streamErr); 539 540 channel.connect(); 541 542 // wait for it to finish 543 _thread = new Thread() { 544 public void run() { 545 int offset = 0; 546 String current = ""; 547 while (!channel.isEOF()) { 548 if (_thread == null) { 549 return; 550 } 551 try { 552 sleep(500); 553 if (streaming) { 554 // System.out.println(streamOut.size()); 555 byte[] stream = streamOut.toByteArray(); 556 int len = stream.length; 557 current += new String(stream, offset, len 558 - offset); 559 // System.out.println(current); 560 offset = len; 561 current = _sendStreamOutput(current); 562 // stdout.send(0,new StringToken(current)); 563 } 564 565 } catch (Exception e) { 566 System.out.println(e.getMessage()); 567 } 568 } 569 try { 570 if (streaming) { 571 byte[] stream = streamOut.toByteArray(); 572 int len = stream.length; 573 current += new String(stream, offset, len - offset); 574 // System.out.println(current); 575 offset = len; 576 current = _sendStreamOutput(current); 577 if (!current.equals("")) { 578 // there was some output that wasn't sent yet. 579 stdout.send(0, new StringToken(current)); 580 } 581 } 582 } catch (Exception ex) { 583 } 584 } 585 }; 586 587 _thread.start(); 588 _thread.join(0); // set this to a different value to time out 589 590 if (_thread.isAlive()) { 591 // ran out of time 592 _thread = null; 593 throw new IllegalActionException("In " + this.getFullName() 594 + ": Remote operation timed out!"); 595 } 596 // completed successfully 597 598 // this is the wrong test if the remote OS is OpenVMS, 599 // but there doesn't seem to be a way to detect it. 600 int ec = channel.getExitStatus(); 601 channel.disconnect(); 602 603 if (!streaming) { 604 stdout.send(0, new StringToken(streamOut.toString())); 605 } 606 stderr.send(0, new StringToken(streamErr.toString())); 607 returncode.send(0, new IntToken(ec)); 608 errors.send(0, new StringToken("")); 609 610 } catch (Exception e) { 611 System.err.println("Exception caught in " + this.getFullName()); 612 e.printStackTrace(); 613 throw new IllegalActionException("Exception caught in " 614 + this.getFullName() + "\n(" + e.getClass().getName() 615 + ")\n" + e.getMessage()); 616 } 617 } 618 619 /** 620 * This function streams the output line by line. If the current stream 621 * doesn't contain any line breaks and it is longer then a certain 622 * threshold, sends the stream content. Otherwise, if there is no line end, 623 * then it returns the content of the last line. 624 * 625 * @param currentStream 626 * */ 627 public String _sendStreamOutput(String currentStream) 628 throws IllegalActionException { 629 String line; 630 int crInd; 631 while ((crInd = currentStream.indexOf("\n")) > -1) { 632 line = currentStream.substring(0, crInd); 633 // System.out.println(line); 634 try { 635 currentStream = currentStream.substring(crInd + 1); 636 } catch (Exception ex) { 637 // reached end of string. 638 // System.out.println(currentStream); 639 currentStream = ""; 640 } 641 stdout.send(0, new StringToken(line)); 642 } 643 644 // if the whole output is a single line, stream it once the string 645 // length reaches a certain threshold. 646 if (currentStream.length() > 2048) { 647 stdout.send(0, new StringToken(currentStream)); 648 } else { 649 // this line doesn't reach the threshold and contains no line break 650 // it will be concatenated to the further output. 651 return currentStream; 652 } 653 return ""; 654 } 655 656 // //////////////// Private variables /////////////////////// 657 658 private JSch _jsch = null; 659 private Session _session = null; 660 private Thread _thread = null; 661 private HashSet _setIdentities = null; 662 private String _strOldUser = null; 663 private String _strOldHost = null; 664 665 // ////////////////Public Static /////////////////////// 666 // Used to store connection info 667 public static Hashtable hash = new Hashtable(); 668 669 // //////////////// Inner classes /////////////////////// 670 671 public static class MyUserInfo implements UserInfo, UIKeyboardInteractive { 672 673 public String getPassword() { 674 return passwd; 675 } 676 677 String passwd = null; 678 JTextField passwordField = (JTextField) new JPasswordField(20); 679 680 public boolean promptYesNo(String str) { 681 // This method gets called to answer the question similar to 682 // "are you sure you want to connect to host whose key 683 // is not in database ..." 684 return true; 685 } 686 687 public String getPassphrase() { 688 return null; 689 } 690 691 public boolean promptPassphrase(String message) { 692 return false; 693 } 694 695 public boolean promptPassword(String message) { 696 if (passwd != null) { 697 return true; 698 } 699 700 Object[] ob = { passwordField }; 701 int result = JOptionPane.showConfirmDialog(null, ob, message, 702 JOptionPane.OK_CANCEL_OPTION); 703 if (result == JOptionPane.OK_OPTION) { 704 passwd = passwordField.getText(); 705 return true; 706 } else { 707 return false; 708 } 709 } 710 711 public void showMessage(String message) { 712 // This method gets called when the server sends over a MOTD. 713 // MessageHandler.message(message); 714 } 715 716 // 717 // Extensions for supporting keyboard-interactive logins 718 // Norbert Podhorszki pnorbert@cs.ucdavis.edu 719 // Taken from example 720 // http://www.jcraft.com/jsch/examples/UserAuthKI.java 721 // 722 723 final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1, 724 GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, 725 new Insets(0, 0, 0, 0), 0, 0); 726 private Container panel; 727 728 public String[] promptKeyboardInteractive(String destination, 729 String name, String instruction, String[] prompt, boolean[] echo) { 730 731 // System.out.println("SSH: promptKI called\n"+ 732 // "\tDestination: " + destination + 733 // "\n\tName: " + name + 734 // "\n\tinstruction: " + instruction + 735 // "\n\tpromptlen: " + prompt.length); 736 737 panel = new JPanel(); 738 panel.setLayout(new GridBagLayout()); 739 740 gbc.weightx = 1.0; 741 gbc.gridwidth = GridBagConstraints.REMAINDER; 742 gbc.gridx = 0; 743 panel.add(new JLabel(instruction), gbc); 744 gbc.gridy++; 745 746 gbc.gridwidth = GridBagConstraints.RELATIVE; 747 748 JTextField[] texts = new JTextField[prompt.length]; 749 for (int i = 0; i < prompt.length; i++) { 750 gbc.fill = GridBagConstraints.NONE; 751 gbc.gridx = 0; 752 gbc.weightx = 1; 753 panel.add(new JLabel(prompt[i]), gbc); 754 755 gbc.gridx = 1; 756 gbc.fill = GridBagConstraints.HORIZONTAL; 757 gbc.weighty = 1; 758 if (echo[i]) { 759 texts[i] = new JTextField(20); 760 } else { 761 texts[i] = new JPasswordField(20); 762 } 763 panel.add(texts[i], gbc); 764 gbc.gridy++; 765 } 766 767 if (JOptionPane.showConfirmDialog(null, panel, destination + ": " 768 + name, JOptionPane.OK_CANCEL_OPTION, 769 JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) { 770 String[] response = new String[prompt.length]; 771 for (int i = 0; i < prompt.length; i++) { 772 response[i] = texts[i].getText(); 773 } 774 return response; 775 } else { 776 return null; // cancel 777 } 778 } 779 780 } 781 782 private ByteArrayOutputStream streamOut; 783 private boolean streaming = false; 784} 785 786// vim: sw=4 ts=4 et