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 }