001 /* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2013 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see http://www.gnu.org/licenses. 027 */ 028 package edu.wisc.ssec.mcidasv.servermanager; 029 030 import static javax.swing.GroupLayout.DEFAULT_SIZE; 031 import static javax.swing.GroupLayout.PREFERRED_SIZE; 032 import static javax.swing.GroupLayout.Alignment.BASELINE; 033 import static javax.swing.GroupLayout.Alignment.LEADING; 034 import static javax.swing.GroupLayout.Alignment.TRAILING; 035 import static javax.swing.LayoutStyle.ComponentPlacement.RELATED; 036 import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED; 037 038 import static edu.wisc.ssec.mcidasv.util.Contract.notNull; 039 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashSet; 040 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newMap; 041 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.set; 042 import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.runOnEDT; 043 044 import java.awt.Color; 045 import java.util.Collection; 046 import java.util.Collections; 047 import java.util.EnumSet; 048 import java.util.LinkedHashSet; 049 import java.util.LinkedHashMap; 050 import java.util.List; 051 import java.util.Map; 052 import java.util.Set; 053 import java.util.StringTokenizer; 054 import java.util.concurrent.Callable; 055 import java.util.concurrent.CompletionService; 056 import java.util.concurrent.ExecutionException; 057 import java.util.concurrent.ExecutorCompletionService; 058 import java.util.concurrent.ExecutorService; 059 import java.util.concurrent.Executors; 060 061 import javax.swing.SwingUtilities; 062 063 import org.slf4j.Logger; 064 import org.slf4j.LoggerFactory; 065 066 import ucar.unidata.util.LogUtil; 067 068 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EditorAction; 069 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntrySource; 070 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType; 071 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryValidity; 072 import edu.wisc.ssec.mcidasv.servermanager.RemoteEntryEditor.AddeStatus; 073 import edu.wisc.ssec.mcidasv.util.CollectionHelpers; 074 import edu.wisc.ssec.mcidasv.util.Contract; 075 import edu.wisc.ssec.mcidasv.util.McVTextField; 076 077 /** 078 * Simple dialog that allows the user to define or modify {@link RemoteAddeEntry}s. 079 * 080 * Temporary solution for adding entries via the adde choosers. 081 */ 082 @SuppressWarnings("serial") 083 public class RemoteEntryShortcut extends javax.swing.JDialog { 084 085 /** Logger object. */ 086 private static final Logger logger = LoggerFactory.getLogger(RemoteEntryShortcut.class); 087 088 /** Possible entry verification states. */ 089 // public enum AddeStatus { PREFLIGHT, BAD_SERVER, BAD_ACCOUNTING, NO_METADATA, OK, BAD_GROUP }; 090 091 /** Number of threads in the thread pool. */ 092 private static final int POOL = 5; 093 094 /** Whether or not to input in the dataset, username, and project fields should be uppercased. */ 095 private static final String PREF_FORCE_CAPS = "mcv.servers.forcecaps"; 096 097 /** Background {@link java.awt.Color Color} of an {@literal "invalid"} {@link javax.swing.JTextField JTextField}. */ 098 private static final Color ERROR_FIELD_COLOR = Color.PINK; 099 100 /** Text {@link java.awt.Color Color} of an {@literal "invalid"} {@link javax.swing.JTextField JTextField}. */ 101 private static final Color ERROR_TEXT_COLOR = Color.WHITE; 102 103 /** Background {@link java.awt.Color Color} of a {@literal "valid"} {@link javax.swing.JTextField JTextField}. */ 104 private static final Color NORMAL_FIELD_COLOR = Color.WHITE; 105 106 /** Text {@link java.awt.Color Color} of a {@literal "valid"} {@link javax.swing.JTextField JTextField}. */ 107 private static final Color NORMAL_TEXT_COLOR = Color.BLACK; 108 109 /** 110 * Contains any {@code JTextField}s that may be in an invalid 111 * (to McIDAS-V) state. 112 */ 113 private final Set<javax.swing.JTextField> badFields = newLinkedHashSet(); 114 115 /** Reference back to the server manager. */ 116 private final EntryStore entryStore; 117 118 /** Current contents of the editor. */ 119 private final Set<RemoteAddeEntry> currentEntries = newLinkedHashSet(); 120 121 /** The last dialog action performed by the user. */ 122 private EditorAction editorAction = EditorAction.INVALID; 123 124 /** Initial contents of {@link #serverField}. Be aware that {@code null} is allowed. */ 125 private final String serverText; 126 127 /** Initial contents of {@link #datasetField}. Be aware that {@code null} is allowed. */ 128 private final String datasetText; 129 130 /** Whether or not the editor is prompting the user to adjust input. */ 131 private boolean inErrorState = false; 132 133 // if we decide to restore error overlays for known "bad" values. 134 // private Set<RemoteAddeEntry> invalidEntries = CollectionHelpers.newLinkedHashSet(); 135 136 /** 137 * Populates the server and dataset text fields with given {@link String}s. 138 * This only works if the dialog <b>is not yet visible</b>. 139 * 140 * <p>This is mostly useful when adding an entry from a chooser. 141 * 142 * @param address Should be the address of a server, but empty and 143 * {@code null} values are allowed. 144 * @param group Should be the name of a group/dataset on {@code server}, 145 * but empty and {@code null} values are allowed. 146 */ 147 public RemoteEntryShortcut(EntryStore entryStore, String address, String group) { 148 super((javax.swing.JDialog)null, true); 149 this.entryStore = entryStore; 150 this.serverText = address; 151 this.datasetText = group; 152 initComponents(RemoteAddeEntry.INVALID_ENTRIES); 153 } 154 155 // TODO(jon): hold back on javadocs, this is likely to change 156 public RemoteEntryShortcut(java.awt.Frame parent, boolean modal, final TabbedAddeManager manager, final EntryStore store) { 157 this(parent, modal, manager, store, RemoteAddeEntry.INVALID_ENTRIES); 158 } 159 160 public RemoteEntryShortcut(java.awt.Frame parent, boolean modal, final TabbedAddeManager manager, final EntryStore store, final RemoteAddeEntry entry) { 161 this(parent, modal, manager, store, CollectionHelpers.list(entry)); 162 } 163 164 // TODO(jon): hold back on javadocs, this is likely to change 165 public RemoteEntryShortcut(java.awt.Frame parent, boolean modal, final TabbedAddeManager manager, final EntryStore store, final List<RemoteAddeEntry> entries) { 166 super(manager, modal); 167 this.entryStore = store; 168 this.serverText = null; 169 this.datasetText = null; 170 if (entries != RemoteAddeEntry.INVALID_ENTRIES) { 171 currentEntries.addAll(entries); 172 } 173 initComponents(entries); 174 } 175 176 /** 177 * Poll the various UI components and attempt to construct valid ADDE 178 * entries based upon the information provided by the user. 179 * 180 * @param ignoreCheckboxes Whether or not the {@literal "type"} checkboxes 181 * should get ignored. Setting this to {@code true} means that <i>all</i> 182 * types are considered valid--which is useful when attempting to verify 183 * the user's input. 184 * 185 * @return {@link Set} of entries that represent the user's input, or an 186 * empty {@code Set} if the input was invalid somehow. 187 */ 188 private Set<RemoteAddeEntry> pollWidgets(final boolean ignoreCheckboxes) { 189 String host = serverField.getText().trim(); 190 String dataset = datasetField.getText().trim(); 191 String username = RemoteAddeEntry.DEFAULT_ACCOUNT.getUsername(); 192 String project = RemoteAddeEntry.DEFAULT_ACCOUNT.getProject(); 193 if (acctBox.isSelected()) { 194 username = userField.getText().trim(); 195 project = projField.getText().trim(); 196 } 197 198 // determine the "valid" types 199 Set<EntryType> selectedTypes = newLinkedHashSet(); 200 if (!ignoreCheckboxes) { 201 if (imageBox.isSelected()) { 202 selectedTypes.add(EntryType.IMAGE); 203 } 204 if (pointBox.isSelected()) { 205 selectedTypes.add(EntryType.POINT); 206 } 207 if (gridBox.isSelected()) { 208 selectedTypes.add(EntryType.GRID); 209 } 210 if (textBox.isSelected()) { 211 selectedTypes.add(EntryType.TEXT); 212 } 213 if (navBox.isSelected()) { 214 selectedTypes.add(EntryType.NAV); 215 } 216 if (radarBox.isSelected()) { 217 selectedTypes.add(EntryType.RADAR); 218 } 219 } else { 220 selectedTypes.addAll(set(EntryType.IMAGE, EntryType.POINT, EntryType.GRID, EntryType.TEXT, EntryType.NAV, EntryType.RADAR)); 221 } 222 223 if (selectedTypes.isEmpty()) { 224 selectedTypes.add(EntryType.UNKNOWN); 225 } 226 227 // deal with the user trying to add multiple groups at once (even though this UI doesn't work right with it) 228 StringTokenizer tok = new StringTokenizer(dataset, ","); 229 Set<String> newDatasets = newLinkedHashSet(); 230 while (tok.hasMoreTokens()) { 231 newDatasets.add(tok.nextToken().trim()); 232 } 233 234 // create a new entry for each group and its valid types. 235 Set<RemoteAddeEntry> entries = newLinkedHashSet(); 236 for (String newGroup : newDatasets) { 237 for (EntryType type : selectedTypes) { 238 RemoteAddeEntry.Builder builder = new RemoteAddeEntry.Builder(host, newGroup).type(type).validity(EntryValidity.VERIFIED).source(EntrySource.USER); 239 if (acctBox.isSelected()) { 240 builder = builder.account(username, project); 241 } 242 RemoteAddeEntry newEntry = builder.build(); 243 List<AddeEntry> matches = entryStore.searchWithPrefix(newEntry.asStringId()); 244 if (matches.isEmpty()) { 245 entries.add(newEntry); 246 } else if (matches.size() == 1) { 247 AddeEntry matchedEntry = matches.get(0); 248 if (matchedEntry.getEntrySource() != EntrySource.SYSTEM) { 249 entries.add(newEntry); 250 } else { 251 entries.add((RemoteAddeEntry)matchedEntry); 252 } 253 } else { 254 // results should only be empty or a single entry 255 logger.warn("server manager returned unexpected results={}", matches); 256 } 257 } 258 } 259 return entries; 260 } 261 262 private void disposeDisplayable(final boolean refreshManager) { 263 if (isDisplayable()) { 264 dispose(); 265 } 266 TabbedAddeManager tmpController = TabbedAddeManager.getTabbedManager(); 267 if (refreshManager && tmpController != null) { 268 tmpController.refreshDisplay(); 269 } 270 } 271 272 /** 273 * Creates new {@link RemoteAddeEntry}s based upon the contents of the dialog 274 * and adds {@literal "them"} to the managed servers. If the dialog is 275 * displayed, we call {@link #dispose()} and attempt to refresh the 276 * server manager GUI if it is available. 277 */ 278 private void addEntry() { 279 Set<RemoteAddeEntry> addedEntries = pollWidgets(false); 280 entryStore.addEntries(addedEntries); 281 disposeDisplayable(true); 282 } 283 284 /** 285 * Replaces the entries within {@link #currentEntries} with new entries 286 * from {@link #pollWidgets(boolean)}. If the dialog is displayed, we call 287 * {@link #dispose()} and attempt to refresh the server manager GUI if it's 288 * available. 289 */ 290 private void editEntry() { 291 Set<RemoteAddeEntry> newEntries = pollWidgets(false); 292 entryStore.replaceEntries(currentEntries, newEntries); 293 logger.trace("currentEntries={}", currentEntries); 294 disposeDisplayable(true); 295 } 296 297 /** 298 * Attempts to verify that the current contents of the GUI are 299 * {@literal "valid"}. 300 */ 301 private void verifyInput() { 302 resetBadFields(); 303 Set<RemoteAddeEntry> unverifiedEntries = pollWidgets(true); 304 305 // the editor GUI only works with one server address at a time. so 306 // although there may be several RemoteAddeEntry objs, they'll all have 307 // the same address and the follow *isn't* as dumb as it looks! 308 if (!unverifiedEntries.isEmpty()) { 309 if (!RemoteAddeEntry.checkHost(unverifiedEntries.toArray(new RemoteAddeEntry[0])[0])) { 310 setStatus("Could not connect to the given server."); 311 setBadField(serverField, true); 312 return; 313 } 314 } else { 315 setStatus("Please specify "); 316 setBadField(serverField, true); 317 return; 318 } 319 320 setStatus("Contacting server..."); 321 Set<RemoteAddeEntry> verifiedEntries = checkGroups(unverifiedEntries); 322 EnumSet<EntryType> presentTypes = EnumSet.noneOf(EntryType.class); 323 if (!verifiedEntries.isEmpty()) { 324 for (RemoteAddeEntry verifiedEntry : verifiedEntries) { 325 presentTypes.add(verifiedEntry.getEntryType()); 326 } 327 imageBox.setSelected(presentTypes.contains(EntryType.IMAGE)); 328 pointBox.setSelected(presentTypes.contains(EntryType.POINT)); 329 gridBox.setSelected(presentTypes.contains(EntryType.GRID)); 330 textBox.setSelected(presentTypes.contains(EntryType.TEXT)); 331 navBox.setSelected(presentTypes.contains(EntryType.NAV)); 332 radarBox.setSelected(presentTypes.contains(EntryType.RADAR)); 333 } 334 } 335 336 /** 337 * Displays a short status message in {@link #statusLabel}. 338 * 339 * @param msg Status message. Shouldn't be {@code null}. 340 */ 341 private void setStatus(final String msg) { 342 assert msg != null; 343 logger.debug("msg={}", msg); 344 runOnEDT(new Runnable() { 345 public void run() { 346 statusLabel.setText(msg); 347 } 348 }); 349 statusLabel.revalidate(); 350 } 351 352 /** 353 * Marks a {@code JTextField} as {@literal "valid"} or {@literal "invalid"}. 354 * Mostly this just means that the field is highlighted in order to provide 355 * to the user a sense of {@literal "what do I fix"} when something goes 356 * wrong. 357 * 358 * @param field {@code JTextField} to mark. 359 * @param isBad {@code true} means that the field is {@literal "invalid"}, 360 * {@code false} means that the field is {@literal "valid"}. 361 */ 362 private void setBadField(final javax.swing.JTextField field, final boolean isBad) { 363 assert field != null; 364 assert field == serverField || field == datasetField || field == userField || field == projField; 365 366 if (isBad) { 367 badFields.add(field); 368 } else { 369 badFields.remove(field); 370 } 371 372 runOnEDT(new Runnable() { 373 public void run() { 374 if (isBad) { 375 field.setForeground(ERROR_TEXT_COLOR); 376 field.setBackground(ERROR_FIELD_COLOR); 377 } else { 378 field.setForeground(NORMAL_TEXT_COLOR); 379 field.setBackground(NORMAL_FIELD_COLOR); 380 } 381 } 382 }); 383 field.revalidate(); 384 } 385 386 /** 387 * Determines whether or not any fields are in an invalid state. Useful 388 * for disallowing the user to add invalid entries to the server manager. 389 * 390 * @return Whether or not any fields are invalid. 391 */ 392 private boolean anyBadFields() { 393 assert badFields != null; 394 return !badFields.isEmpty(); 395 } 396 397 /** 398 * Clear out {@link #badFields} and {@literal "set"} the field's status to 399 * valid. 400 */ 401 private void resetBadFields() { 402 Set<javax.swing.JTextField> fields = new LinkedHashSet<javax.swing.JTextField>(badFields); 403 for (javax.swing.JTextField field : fields) { 404 setBadField(field, false); 405 } 406 } 407 408 /** 409 * @see #editorAction 410 */ 411 public EditorAction getEditorAction() { 412 return editorAction; 413 } 414 415 /** 416 * @see #editorAction 417 */ 418 private void setEditorAction(final EditorAction editorAction) { 419 this.editorAction = editorAction; 420 } 421 422 /** 423 * Controls the value associated with the {@link #PREF_FORCE_CAPS} preference. 424 * 425 * @param value {@code true} causes user input into the dataset, username, 426 * and project fields to be capitalized. 427 * 428 * @see #getForceMcxCaps() 429 */ 430 private void setForceMcxCaps(final boolean value) { 431 entryStore.getIdvStore().put(PREF_FORCE_CAPS, value); 432 } 433 434 /** 435 * Returns the value associated with the {@link #PREF_FORCE_CAPS} preference. 436 * 437 * @see #setForceMcxCaps(boolean) 438 */ 439 private boolean getForceMcxCaps() { 440 return entryStore.getIdvStore().get(PREF_FORCE_CAPS, true); 441 } 442 443 // TODO(jon): oh man clean this junk up 444 /** This method is called from within the constructor to 445 * initialize the form. 446 * WARNING: Do NOT modify this code. The content of this method is 447 * always regenerated by the Form Editor. 448 */ 449 @SuppressWarnings("unchecked") 450 // <editor-fold defaultstate="collapsed" desc="Generated Code"> 451 private void initComponents(final List<RemoteAddeEntry> initEntries) { 452 assert SwingUtilities.isEventDispatchThread(); 453 entryPanel = new javax.swing.JPanel(); 454 serverLabel = new javax.swing.JLabel(); 455 serverField = new javax.swing.JTextField(); 456 datasetLabel = new javax.swing.JLabel(); 457 datasetField = new McVTextField(); 458 acctBox = new javax.swing.JCheckBox(); 459 userLabel = new javax.swing.JLabel(); 460 userField = new McVTextField(); 461 projLabel = new javax.swing.JLabel(); 462 projField = new javax.swing.JTextField(); 463 capBox = new javax.swing.JCheckBox(); 464 typePanel = new javax.swing.JPanel(); 465 imageBox = new javax.swing.JCheckBox(); 466 pointBox = new javax.swing.JCheckBox(); 467 gridBox = new javax.swing.JCheckBox(); 468 textBox = new javax.swing.JCheckBox(); 469 navBox = new javax.swing.JCheckBox(); 470 radarBox = new javax.swing.JCheckBox(); 471 statusPanel = new javax.swing.JPanel(); 472 statusLabel = new javax.swing.JLabel(); 473 verifyAddButton = new javax.swing.JButton(); 474 verifyServer = new javax.swing.JButton(); 475 addServer = new javax.swing.JButton(); 476 cancelButton = new javax.swing.JButton(); 477 478 boolean forceCaps = getForceMcxCaps(); 479 datasetField.setUppercase(forceCaps); 480 userField.setUppercase(forceCaps); 481 482 if (initEntries == RemoteAddeEntry.INVALID_ENTRIES) { 483 setTitle("Define New Remote Dataset"); 484 } else { 485 setTitle("Edit Remote Dataset"); 486 } 487 setResizable(false); 488 setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); 489 addWindowListener(new java.awt.event.WindowAdapter() { 490 public void windowClosed(java.awt.event.WindowEvent evt) { 491 formWindowClosed(evt); 492 } 493 }); 494 495 serverLabel.setText("Server:"); 496 if (serverText != null) { 497 serverField.setText(serverText); 498 } 499 500 datasetLabel.setText("Dataset:"); 501 if (datasetText != null) { 502 datasetField.setText(datasetText); 503 } 504 505 acctBox.setText("Specify accounting information:"); 506 acctBox.addActionListener(new java.awt.event.ActionListener() { 507 public void actionPerformed(java.awt.event.ActionEvent evt) { 508 acctBoxActionPerformed(evt); 509 } 510 }); 511 512 userLabel.setText("Username:"); 513 userField.setEnabled(acctBox.isSelected()); 514 515 projLabel.setText("Project #:"); 516 projField.setEnabled(acctBox.isSelected()); 517 518 capBox.setText("Automatically capitalize dataset and username?"); 519 capBox.setSelected(forceCaps); 520 capBox.addActionListener(new java.awt.event.ActionListener() { 521 public void actionPerformed(java.awt.event.ActionEvent evt) { 522 capBoxActionPerformed(evt); 523 } 524 }); 525 526 javax.swing.event.DocumentListener inputListener = new javax.swing.event.DocumentListener() { 527 public void changedUpdate(javax.swing.event.DocumentEvent evt) { 528 reactToValueChanges(); 529 } 530 public void insertUpdate(javax.swing.event.DocumentEvent evt) { 531 if (inErrorState) { 532 verifyAddButton.setEnabled(true); 533 verifyServer.setEnabled(true); 534 inErrorState = false; 535 resetBadFields(); 536 } 537 } 538 public void removeUpdate(javax.swing.event.DocumentEvent evt) { 539 if (inErrorState) { 540 verifyAddButton.setEnabled(true); 541 verifyServer.setEnabled(true); 542 inErrorState = false; 543 resetBadFields(); 544 } 545 } 546 }; 547 548 serverField.getDocument().addDocumentListener(inputListener); 549 datasetField.getDocument().addDocumentListener(inputListener); 550 userField.getDocument().addDocumentListener(inputListener); 551 projField.getDocument().addDocumentListener(inputListener); 552 553 javax.swing.GroupLayout entryPanelLayout = new javax.swing.GroupLayout(entryPanel); 554 entryPanel.setLayout(entryPanelLayout); 555 entryPanelLayout.setHorizontalGroup( 556 entryPanelLayout.createParallelGroup(LEADING) 557 .addGroup(entryPanelLayout.createSequentialGroup() 558 .addGroup(entryPanelLayout.createParallelGroup(LEADING) 559 .addComponent(serverLabel, TRAILING) 560 .addComponent(datasetLabel, TRAILING) 561 .addComponent(userLabel, TRAILING) 562 .addComponent(projLabel, TRAILING)) 563 .addPreferredGap(RELATED) 564 .addGroup(entryPanelLayout.createParallelGroup(LEADING) 565 .addComponent(serverField, DEFAULT_SIZE, 419, Short.MAX_VALUE) 566 .addComponent(capBox) 567 .addComponent(acctBox) 568 .addComponent(datasetField, DEFAULT_SIZE, 419, Short.MAX_VALUE) 569 .addComponent(userField, DEFAULT_SIZE, 419, Short.MAX_VALUE) 570 .addComponent(projField, DEFAULT_SIZE, 419, Short.MAX_VALUE)) 571 .addContainerGap()) 572 ); 573 entryPanelLayout.setVerticalGroup( 574 entryPanelLayout.createParallelGroup(LEADING) 575 .addGroup(entryPanelLayout.createSequentialGroup() 576 .addGroup(entryPanelLayout.createParallelGroup(BASELINE) 577 .addComponent(serverLabel) 578 .addComponent(serverField, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) 579 .addPreferredGap(RELATED) 580 .addGroup(entryPanelLayout.createParallelGroup(BASELINE) 581 .addComponent(datasetLabel) 582 .addComponent(datasetField, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) 583 .addGap(16, 16, 16) 584 .addComponent(acctBox) 585 .addPreferredGap(RELATED) 586 .addGroup(entryPanelLayout.createParallelGroup(BASELINE) 587 .addComponent(userLabel) 588 .addComponent(userField, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) 589 .addPreferredGap(RELATED) 590 .addGroup(entryPanelLayout.createParallelGroup(BASELINE) 591 .addComponent(projLabel) 592 .addComponent(projField, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) 593 .addPreferredGap(RELATED) 594 .addComponent(capBox) 595 .addGap(0, 0, Short.MAX_VALUE)) 596 ); 597 598 typePanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Dataset Types")); 599 600 java.awt.event.ActionListener typeInputListener = new java.awt.event.ActionListener() { 601 public void actionPerformed(java.awt.event.ActionEvent evt) { 602 if (inErrorState) { 603 verifyAddButton.setEnabled(true); 604 verifyServer.setEnabled(true); 605 inErrorState = false; 606 resetBadFields(); 607 } 608 } 609 }; 610 611 imageBox.setText("Image"); 612 imageBox.addActionListener(typeInputListener); 613 typePanel.add(imageBox); 614 615 pointBox.setText("Point"); 616 pointBox.addActionListener(typeInputListener); 617 typePanel.add(pointBox); 618 619 gridBox.setText("Grid"); 620 gridBox.addActionListener(typeInputListener); 621 typePanel.add(gridBox); 622 623 textBox.setText("Text"); 624 textBox.addActionListener(typeInputListener); 625 typePanel.add(textBox); 626 627 navBox.setText("Navigation"); 628 navBox.addActionListener(typeInputListener); 629 typePanel.add(navBox); 630 631 radarBox.setText("Radar"); 632 radarBox.addActionListener(typeInputListener); 633 typePanel.add(radarBox); 634 635 statusPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Status")); 636 637 statusLabel.setText("Please provide the address of a remote ADDE server."); 638 639 javax.swing.GroupLayout statusPanelLayout = new javax.swing.GroupLayout(statusPanel); 640 statusPanel.setLayout(statusPanelLayout); 641 statusPanelLayout.setHorizontalGroup( 642 statusPanelLayout.createParallelGroup(LEADING) 643 .addGroup(statusPanelLayout.createSequentialGroup() 644 .addContainerGap() 645 .addComponent(statusLabel) 646 .addContainerGap(154, Short.MAX_VALUE)) 647 ); 648 statusPanelLayout.setVerticalGroup( 649 statusPanelLayout.createParallelGroup(LEADING) 650 .addGroup(statusPanelLayout.createSequentialGroup() 651 .addComponent(statusLabel) 652 .addContainerGap(DEFAULT_SIZE, Short.MAX_VALUE)) 653 ); 654 655 if (initEntries == RemoteAddeEntry.INVALID_ENTRIES) { 656 verifyAddButton.setText("Verify and Add Server"); 657 } else { 658 verifyAddButton.setText("Verify and Save Changes"); 659 } 660 verifyAddButton.addActionListener(new java.awt.event.ActionListener() { 661 public void actionPerformed(java.awt.event.ActionEvent evt) { 662 if (initEntries == RemoteAddeEntry.INVALID_ENTRIES) 663 verifyAddButtonActionPerformed(evt); 664 else 665 verifyEditButtonActionPerformed(evt); 666 } 667 }); 668 669 if (initEntries == RemoteAddeEntry.INVALID_ENTRIES) { 670 verifyServer.setText("Verify Server"); 671 } else { 672 verifyServer.setText("Verify Changes"); 673 } 674 verifyServer.addActionListener(new java.awt.event.ActionListener() { 675 public void actionPerformed(java.awt.event.ActionEvent evt) { 676 verifyServerActionPerformed(evt); 677 } 678 }); 679 680 if (initEntries == RemoteAddeEntry.INVALID_ENTRIES) { 681 addServer.setText("Add Server"); 682 } else { 683 addServer.setText("Save Changes"); 684 } 685 addServer.addActionListener(new java.awt.event.ActionListener() { 686 public void actionPerformed(java.awt.event.ActionEvent evt) { 687 if (initEntries == RemoteAddeEntry.INVALID_ENTRIES) { 688 addServerActionPerformed(evt); 689 } else { 690 editServerActionPerformed(evt); 691 } 692 } 693 }); 694 695 cancelButton.setText("Cancel"); 696 cancelButton.addActionListener(new java.awt.event.ActionListener() { 697 public void actionPerformed(java.awt.event.ActionEvent evt) { 698 cancelButtonActionPerformed(evt); 699 } 700 }); 701 702 javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); 703 getContentPane().setLayout(layout); 704 layout.setHorizontalGroup( 705 layout.createParallelGroup(LEADING) 706 .addGroup(layout.createSequentialGroup() 707 .addContainerGap() 708 .addGroup(layout.createParallelGroup(LEADING) 709 .addComponent(statusPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 710 .addComponent(typePanel, 0, 0, Short.MAX_VALUE) 711 .addComponent(entryPanel, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE) 712 .addGroup(layout.createSequentialGroup() 713 .addComponent(verifyAddButton) 714 .addPreferredGap(RELATED) 715 .addComponent(verifyServer) 716 .addPreferredGap(RELATED) 717 .addComponent(addServer) 718 .addPreferredGap(RELATED) 719 .addComponent(cancelButton))) 720 .addContainerGap()) 721 ); 722 layout.setVerticalGroup( 723 layout.createParallelGroup(LEADING) 724 .addGroup(layout.createSequentialGroup() 725 .addContainerGap() 726 .addComponent(entryPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE) 727 .addPreferredGap(UNRELATED) 728 .addComponent(typePanel, PREFERRED_SIZE, 57, PREFERRED_SIZE) 729 .addGap(18, 18, 18) 730 .addComponent(statusPanel, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE) 731 .addGap(18, 18, 18) 732 .addGroup(layout.createParallelGroup(BASELINE) 733 .addComponent(verifyServer) 734 .addComponent(addServer) 735 .addComponent(cancelButton) 736 .addComponent(verifyAddButton)) 737 .addContainerGap(17, Short.MAX_VALUE)) 738 ); 739 740 if (initEntries != null && !RemoteAddeEntry.INVALID_ENTRIES.equals(initEntries)) { 741 RemoteAddeEntry initEntry = initEntries.get(0); 742 boolean hasSystemEntry = false; 743 for (RemoteAddeEntry entry : initEntries) { 744 if (entry.getEntrySource() == EntrySource.SYSTEM) { 745 initEntry = entry; 746 hasSystemEntry = true; 747 break; 748 } 749 } 750 serverField.setText(initEntry.getAddress()); 751 datasetField.setText(initEntry.getGroup()); 752 753 if (!RemoteAddeEntry.DEFAULT_ACCOUNT.equals(initEntry.getAccount())) { 754 acctBox.setSelected(true); 755 userField.setEnabled(true); 756 userField.setText(initEntry.getAccount().getUsername()); 757 projField.setEnabled(true); 758 projField.setText(initEntry.getAccount().getProject()); 759 } 760 761 if (hasSystemEntry) { 762 serverField.setEnabled(false); 763 datasetField.setEnabled(false); 764 acctBox.setEnabled(false); 765 userField.setEnabled(false); 766 projField.setEnabled(false); 767 capBox.setEnabled(false); 768 } 769 770 for (RemoteAddeEntry entry : initEntries) { 771 boolean nonDefaultSource = entry.getEntrySource() != EntrySource.SYSTEM; 772 if (entry.getEntryType() == EntryType.IMAGE) { 773 imageBox.setSelected(true); 774 imageBox.setEnabled(nonDefaultSource); 775 } else if (entry.getEntryType() == EntryType.POINT) { 776 pointBox.setSelected(true); 777 pointBox.setEnabled(nonDefaultSource); 778 } else if (entry.getEntryType() == EntryType.GRID) { 779 gridBox.setSelected(true); 780 gridBox.setEnabled(nonDefaultSource); 781 } else if (entry.getEntryType() == EntryType.TEXT) { 782 textBox.setSelected(true); 783 textBox.setEnabled(nonDefaultSource); 784 } else if (entry.getEntryType() == EntryType.NAV) { 785 navBox.setSelected(true); 786 navBox.setEnabled(nonDefaultSource); 787 } else if (entry.getEntryType() == EntryType.RADAR) { 788 radarBox.setSelected(true); 789 radarBox.setEnabled(nonDefaultSource); 790 } 791 } 792 } 793 pack(); 794 }// </editor-fold> 795 796 private void acctBoxActionPerformed(java.awt.event.ActionEvent evt) { 797 assert SwingUtilities.isEventDispatchThread(); 798 resetBadFields(); 799 boolean enabled = acctBox.isSelected(); 800 userField.setEnabled(enabled); 801 projField.setEnabled(enabled); 802 verifyAddButton.setEnabled(true); 803 verifyServer.setEnabled(true); 804 } 805 806 private void capBoxActionPerformed(java.awt.event.ActionEvent evt) { 807 assert SwingUtilities.isEventDispatchThread(); 808 boolean forceCaps = capBox.isSelected(); 809 datasetField.setUppercase(forceCaps); 810 userField.setUppercase(forceCaps); 811 setForceMcxCaps(forceCaps); 812 if (!forceCaps) { 813 return; 814 } 815 datasetField.setText(datasetField.getText().toUpperCase()); 816 userField.setText(userField.getText().toUpperCase()); 817 } 818 819 private void verifyAddButtonActionPerformed(java.awt.event.ActionEvent evt) { 820 verifyInput(); 821 if (!anyBadFields()) { 822 setEditorAction(EditorAction.ADDED_VERIFIED); 823 addEntry(); 824 } else { 825 inErrorState = true; 826 verifyAddButton.setEnabled(false); 827 verifyServer.setEnabled(false); 828 } 829 } 830 831 private void verifyEditButtonActionPerformed(java.awt.event.ActionEvent evt) { 832 verifyInput(); 833 if (!anyBadFields()) { 834 setEditorAction(EditorAction.EDITED_VERIFIED); 835 editEntry(); 836 } else { 837 inErrorState = true; 838 verifyAddButton.setEnabled(false); 839 verifyServer.setEnabled(false); 840 } 841 } 842 843 private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) { 844 setEditorAction(EditorAction.CANCELLED); 845 disposeDisplayable(false); 846 } 847 848 private void formWindowClosed(java.awt.event.WindowEvent evt) { 849 setEditorAction(EditorAction.CANCELLED); 850 disposeDisplayable(false); 851 } 852 853 private void verifyServerActionPerformed(java.awt.event.ActionEvent evt) { 854 verifyInput(); 855 if (anyBadFields()) { 856 // save poll widget state 857 // toggle a "listen for *any* input event" switch to on 858 // invalidEntries.clear(); 859 // invalidEntries.addAll(pollWidgets(false)); 860 inErrorState = true; 861 verifyAddButton.setEnabled(false); 862 verifyServer.setEnabled(false); 863 } 864 } 865 866 private void addServerActionPerformed(java.awt.event.ActionEvent evt) { 867 setEditorAction(EditorAction.ADDED); 868 addEntry(); 869 } 870 871 private void editServerActionPerformed(java.awt.event.ActionEvent evt) { 872 setEditorAction(EditorAction.EDITED); 873 editEntry(); 874 } 875 876 private void reactToValueChanges() { 877 assert SwingUtilities.isEventDispatchThread(); 878 if (inErrorState) { 879 verifyAddButton.setEnabled(true); 880 verifyServer.setEnabled(true); 881 inErrorState = false; 882 resetBadFields(); 883 } 884 } 885 886 /** 887 * Attempt to verify a {@link Set} of {@link RemoteAddeEntry}s. Useful for 888 * checking a {@literal "MCTABLE.TXT"} after importing. 889 * 890 * @param entries {@code Set} of remote ADDE entries to validate. Cannot 891 * be {@code null}. 892 * 893 * @return {@code Set} of {@code RemoteAddeEntry}s that McIDAS-V was able 894 * to connect to. 895 * 896 * @throws NullPointerException if {@code entries} is {@code null}. 897 */ 898 public Set<RemoteAddeEntry> checkHosts(final Set<RemoteAddeEntry> entries) { 899 Contract.notNull(entries, "entries cannot be null"); 900 Set<RemoteAddeEntry> goodEntries = newLinkedHashSet(); 901 Set<String> checkedHosts = newLinkedHashSet(); 902 Map<String, Boolean> hostStatus = newMap(); 903 for (RemoteAddeEntry entry : entries) { 904 String host = entry.getAddress(); 905 if (hostStatus.get(host) == Boolean.FALSE) { 906 continue; 907 } else if (hostStatus.get(host) == Boolean.TRUE) { 908 goodEntries.add(entry); 909 } else { 910 checkedHosts.add(host); 911 if (RemoteAddeEntry.checkHost(entry)) { 912 goodEntries.add(entry); 913 hostStatus.put(host, Boolean.TRUE); 914 } else { 915 hostStatus.put(host, Boolean.FALSE); 916 } 917 } 918 } 919 return goodEntries; 920 } 921 922 public Set<RemoteAddeEntry> checkGroups(final Set<RemoteAddeEntry> entries) { 923 Contract.notNull(entries, "entries cannot be null"); 924 if (entries.isEmpty()) { 925 return Collections.emptySet(); 926 } 927 928 Set<RemoteAddeEntry> verified = newLinkedHashSet(); 929 Collection<AddeStatus> statuses = EnumSet.noneOf(AddeStatus.class); 930 ExecutorService exec = Executors.newFixedThreadPool(POOL); 931 CompletionService<StatusWrapper> ecs = new ExecutorCompletionService<StatusWrapper>(exec); 932 Map<RemoteAddeEntry, AddeStatus> entry2Status = new LinkedHashMap<RemoteAddeEntry, AddeStatus>(entries.size()); 933 934 // submit new verification tasks to the pool's queue ... (apologies for the pun?) 935 for (RemoteAddeEntry entry : entries) { 936 StatusWrapper pairing = new StatusWrapper(entry); 937 ecs.submit(new VerifyEntryTask(pairing)); 938 } 939 940 // use completion service magic to only deal with finished verification tasks 941 try { 942 for (int i = 0; i < entries.size(); i++) { 943 StatusWrapper pairing = ecs.take().get(); 944 RemoteAddeEntry entry = pairing.getEntry(); 945 AddeStatus status = pairing.getStatus(); 946 setStatus(entry.getEntryText()+": attempting verification..."); 947 statuses.add(status); 948 entry2Status.put(entry, status); 949 if (status == AddeStatus.OK) { 950 verified.add(entry); 951 setStatus("Found accessible "+entry.getEntryType().toString().toLowerCase()+" data."); 952 } 953 } 954 } catch (InterruptedException e) { 955 LogUtil.logException("interrupted while checking ADDE entries", e); 956 } catch (ExecutionException e) { 957 LogUtil.logException("ADDE validation execution error", e); 958 } finally { 959 exec.shutdown(); 960 } 961 962 if (!statuses.contains(AddeStatus.OK)) { 963 if (statuses.contains(AddeStatus.BAD_ACCOUNTING)) { 964 setStatus("Incorrect accounting information."); 965 setBadField(userField, true); 966 setBadField(projField, true); 967 } else if (statuses.contains(AddeStatus.BAD_GROUP)) { 968 setStatus("Dataset does not appear to be valid."); 969 setBadField(datasetField, true); 970 } else if (statuses.contains(AddeStatus.BAD_SERVER)) { 971 setStatus("Could not connect to the ADDE server."); 972 setBadField(serverField, true); 973 } else { 974 logger.warn("guru meditation error: statuses={}", statuses); 975 } 976 } else { 977 setStatus("Finished verifying."); 978 } 979 return verified; 980 } 981 982 private static Map<RemoteAddeEntry, AddeStatus> bulkPut(final Collection<RemoteAddeEntry> entries, final AddeStatus status) { 983 Map<RemoteAddeEntry, AddeStatus> map = new LinkedHashMap<RemoteAddeEntry, AddeStatus>(entries.size()); 984 for (RemoteAddeEntry entry : entries) { 985 map.put(entry, status); 986 } 987 return map; 988 } 989 990 /** 991 * Associates a {@link RemoteAddeEntry} with one of the states from 992 * {@link AddeStatus}. 993 */ 994 private static class StatusWrapper { 995 /** */ 996 private final RemoteAddeEntry entry; 997 998 /** Current {@literal "status"} of {@link #entry}. */ 999 private AddeStatus status; 1000 1001 /** 1002 * Builds an entry/status pairing. 1003 * 1004 * @param entry The {@code RemoteAddeEntry} to wrap up. 1005 * 1006 * @throws NullPointerException if {@code entry} is {@code null}. 1007 */ 1008 public StatusWrapper(final RemoteAddeEntry entry) { 1009 notNull(entry, "cannot create a entry/status pair with a null descriptor"); 1010 this.entry = entry; 1011 } 1012 1013 /** 1014 * Set the {@literal "status"} of this {@link #entry} to a given 1015 * {@link AddeStatus}. 1016 * 1017 * @param status New status of {@code entry}. 1018 */ 1019 public void setStatus(AddeStatus status) { 1020 this.status = status; 1021 } 1022 1023 /** 1024 * Returns the current {@literal "status"} of {@link #entry}. 1025 * 1026 * @return One of {@link AddeStatus}. 1027 */ 1028 public AddeStatus getStatus() { 1029 return status; 1030 } 1031 1032 /** 1033 * Returns the {@link RemoteAddeEntry} stored in this wrapper. 1034 * 1035 * @return {@link #entry} 1036 */ 1037 public RemoteAddeEntry getEntry() { 1038 return entry; 1039 } 1040 } 1041 1042 /** 1043 * Represents an ADDE entry verification task. These are executed asynchronously 1044 * by the completion service within {@link RemoteEntryEditor#checkGroups(Set)}. 1045 */ 1046 private class VerifyEntryTask implements Callable<StatusWrapper> { 1047 private final StatusWrapper entryStatus; 1048 public VerifyEntryTask(final StatusWrapper descStatus) { 1049 notNull(descStatus, "cannot verify or set status of a null descriptor/status pair"); 1050 this.entryStatus = descStatus; 1051 } 1052 1053 public StatusWrapper call() throws Exception { 1054 entryStatus.setStatus(RemoteAddeEntry.checkEntry(entryStatus.getEntry())); 1055 return entryStatus; 1056 } 1057 } 1058 1059 // Variables declaration - do not modify 1060 private javax.swing.JCheckBox acctBox; 1061 private javax.swing.JButton addServer; 1062 private javax.swing.JButton cancelButton; 1063 private javax.swing.JCheckBox capBox; 1064 private McVTextField datasetField; 1065 private javax.swing.JLabel datasetLabel; 1066 private javax.swing.JPanel entryPanel; 1067 private javax.swing.JCheckBox gridBox; 1068 private javax.swing.JCheckBox imageBox; 1069 private javax.swing.JCheckBox navBox; 1070 private javax.swing.JCheckBox pointBox; 1071 private javax.swing.JTextField projField; 1072 private javax.swing.JLabel projLabel; 1073 private javax.swing.JCheckBox radarBox; 1074 private javax.swing.JTextField serverField; 1075 private javax.swing.JLabel serverLabel; 1076 private javax.swing.JLabel statusLabel; 1077 private javax.swing.JPanel statusPanel; 1078 private javax.swing.JCheckBox textBox; 1079 private javax.swing.JPanel typePanel; 1080 private McVTextField userField; 1081 private javax.swing.JLabel userLabel; 1082 private javax.swing.JButton verifyAddButton; 1083 private javax.swing.JButton verifyServer; 1084 // End of variables declaration 1085 }