001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 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 https://www.gnu.org/licenses/. 027 */ 028package edu.wisc.ssec.mcidasv.servermanager; 029 030import static java.util.Objects.requireNonNull; 031 032import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 033import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashMap; 034import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashSet; 035 036import java.io.File; 037import java.io.IOException; 038 039import java.nio.file.Path; 040import java.nio.file.Paths; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.EnumSet; 044import java.util.LinkedHashSet; 045import java.util.List; 046import java.util.Map; 047import java.util.Set; 048import java.util.stream.Collectors; 049 050import org.apache.commons.collections4.trie.PatriciaTrie; 051import org.bushe.swing.event.EventBus; 052import org.bushe.swing.event.annotation.AnnotationProcessor; 053import org.bushe.swing.event.annotation.EventSubscriber; 054 055import org.python.modules.posix.PosixModule; 056 057import org.slf4j.Logger; 058import org.slf4j.LoggerFactory; 059 060import org.w3c.dom.Element; 061 062import ucar.unidata.util.LogUtil; 063import ucar.unidata.idv.IdvObjectStore; 064import ucar.unidata.idv.IdvResourceManager; 065import ucar.unidata.idv.chooser.adde.AddeServer; 066import ucar.unidata.xml.XmlResourceCollection; 067 068import edu.wisc.ssec.mcidasv.Constants; 069import edu.wisc.ssec.mcidasv.McIDASV; 070import edu.wisc.ssec.mcidasv.ResourceManager; 071import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntrySource; 072import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryStatus; 073import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType; 074import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryValidity; 075import edu.wisc.ssec.mcidasv.servermanager.AddeThread.McservEvent; 076 077/** 078 * McIDAS-V ADDE server manager. This class is essentially the 079 * {@literal "gatekeeper"} for anything having to do with the application's 080 * collection of ADDE servers. This class is also responsible for controlling 081 * the thread used to manage the external mcservl binary. 082 * 083 * @see AddeThread 084 */ 085public class EntryStore { 086 087 /** 088 * Property that allows users to supply arbitrary paths to McIDAS-X 089 * binaries used by mcservl. 090 * 091 * @see #getAddeRootDirectory() 092 */ 093 private static final String PROP_DEBUG_LOCALROOT = 094 "debug.localadde.rootdir"; 095 096 /** 097 * Property that allows users to control debug output from ADDE requests. 098 * 099 * @see #isAddeDebugEnabled(boolean) 100 * @see #setAddeDebugEnabled(boolean) 101 */ 102 private static final String PROP_DEBUG_ADDEURL = "debug.adde.reqs"; 103 104 /** 105 * {@literal "Userpath"} not writable error message. 106 * This is the one that is shown in the GUI via 107 * {@link LogUtil#userErrorMessage(String)}. 108 */ 109 private static final String ERROR_LOGUTIL_USERPATH = 110 "Local servers cannot write to userpath:\n%s"; 111 112 /** 113 * {@literal "Userpath"} not writable error message. 114 * This one is used by the logging system. 115 */ 116 private static final String ERROR_USERPATH = 117 "Local servers cannot write to userpath ('{}')"; 118 119 /** 120 * SLF4J-style formatting string for use when {@code RESOLV.SRV} can not 121 * be found. . 122 */ 123 private static final String WARN_NO_RESOLVSRV = 124 "EntryStore: RESOLV.SRV missing; expected='{}'"; 125 126 /** Enumeration of the various server manager events. */ 127 public enum Event { 128 /** Entries were replaced. */ 129 REPLACEMENT, 130 /** Entries were removed. */ 131 REMOVAL, 132 /** Entries were added. */ 133 ADDITION, 134 /** Entries were updated.*/ 135 UPDATE, 136 /** Something failed! */ 137 FAILURE, 138 /** Local servers started. */ 139 STARTED, 140 /** Catch-all? */ 141 UNKNOWN 142 } 143 144 /** Logging object. */ 145 private static final Logger logger = 146 LoggerFactory.getLogger(EntryStore.class); 147 148 /** Preference key for ADDE entries. */ 149 private static final String PREF_ADDE_ENTRIES = "mcv.servers.entries"; 150 151 /** The ADDE servers known to McIDAS-V. */ 152 private final PatriciaTrie<AddeEntry> trie; 153 154 /** {@literal "Root"} local server directory. */ 155 private final String ADDE_DIRECTORY; 156 157 /** Path to local server binaries. */ 158 private final String ADDE_BIN; 159 160 /** Path to local server data. */ 161 private final String ADDE_DATA; 162 163 /** Path to mcservl. */ 164 private final String ADDE_MCSERVL; 165 166 /** Path to the user's {@literal "userpath"} directory. */ 167 private final String USER_DIRECTORY; 168 169 /** Path to the user's {@literal "RESOLV.SRV"}. */ 170 private final String ADDE_RESOLV; 171 172 /** Which port is this particular manager operating on */ 173 private static String localPort; 174 175 /** Thread that monitors the mcservl process. */ 176 private static AddeThread thread; 177 178 /** Last {@link AddeEntry AddeEntries} added to the manager. */ 179 private final List<AddeEntry> lastAdded; 180 181 /** McIDAS-V preferences store. */ 182 private final IdvObjectStore idvStore; 183 184 /** 185 * Constructs a server manager. 186 * 187 * @param store McIDAS-V's preferences store. Cannot be {@code null}. 188 * @param rscManager McIDAS-V's resource manager. Cannot be {@code null}. 189 * 190 * @throws NullPointerException if either of {@code store} or 191 * {@code rscManager} is {@code null}. 192 */ 193 public EntryStore(final IdvObjectStore store, 194 final IdvResourceManager rscManager) 195 { 196 requireNonNull(store); 197 requireNonNull(rscManager); 198 199 this.idvStore = store; 200 this.trie = new PatriciaTrie<>(); 201 this.ADDE_DIRECTORY = getAddeRootDirectory(); 202 this.ADDE_BIN = ADDE_DIRECTORY + File.separator + "bin"; 203 this.ADDE_DATA = ADDE_DIRECTORY + File.separator + "data"; 204 EntryStore.localPort = Constants.LOCAL_ADDE_PORT; 205 this.lastAdded = arrList(); 206 AnnotationProcessor.process(this); 207 208 McIDASV mcv = McIDASV.getStaticMcv(); 209 USER_DIRECTORY = mcv.getUserDirectory(); 210 ADDE_RESOLV = mcv.getUserFile("RESOLV.SRV"); 211 212 if (McIDASV.isWindows()) { 213 ADDE_MCSERVL = ADDE_BIN + "\\mcservl.exe"; 214 } else { 215 ADDE_MCSERVL = ADDE_BIN + "/mcservl"; 216 } 217 218 try { 219 Set<LocalAddeEntry> locals = 220 EntryTransforms.readResolvFile(ADDE_RESOLV); 221 putEntries(trie, locals); 222 } catch (IOException e) { 223 logger.warn(WARN_NO_RESOLVSRV, ADDE_RESOLV); 224 } 225 226 XmlResourceCollection userResource = 227 rscManager.getXmlResources(ResourceManager.RSC_NEW_USERSERVERS); 228 XmlResourceCollection sysResource = 229 rscManager.getXmlResources(IdvResourceManager.RSC_ADDESERVER); 230 231 Set<AddeEntry> systemEntries = 232 extractResourceEntries(EntrySource.SYSTEM, sysResource); 233 234 Set<AddeEntry> prefEntries = extractPreferencesEntries(store); 235 prefEntries = removeDeletedSystemEntries(prefEntries, systemEntries); 236 237 Set<AddeEntry> userEntries = extractUserEntries(userResource); 238 userEntries = removeDeletedSystemEntries(userEntries, systemEntries); 239 240 putEntries(trie, systemEntries); 241 putEntries(trie, userEntries); 242 putEntries(trie, prefEntries); 243 244 saveEntries(); 245 } 246 247 /** 248 * Searches {@code entries} for {@link AddeEntry} objects with two characteristics: 249 * <ul> 250 * <li>the object source is {@link EntrySource#SYSTEM}</li> 251 * <li>the object is <b>not</b> in {@code systemEntries}</li> 252 * </ul> 253 * 254 * <p>The intent behind this method is to safely remove {@literal "system"} 255 * entries that have been stored to a user's preferences. {@code entries} 256 * can be generated from anywhere you like, but {@code systemEntries} should 257 * almost always be created from {@literal "addeservers.xml"}. 258 * 259 * @param entries Cannot be {@code null}. 260 * @param systemEntries Cannot be {@code null}. 261 * 262 * @return {@code Set} of entries that are not system resources that have 263 * been removed, or an empty {@code Set}. 264 */ 265 private static Set<AddeEntry> removeDeletedSystemEntries( 266 final Collection<? extends AddeEntry> entries, 267 final Collection<? extends AddeEntry> systemEntries) 268 { 269 Set<AddeEntry> pruned = newLinkedHashSet(entries.size()); 270 for (AddeEntry entry : entries) { 271 if (entry.getEntrySource() != EntrySource.SYSTEM) { 272 pruned.add(entry); 273 } else if (systemEntries.contains(entry)) { 274 pruned.add(entry); 275 } 276 } 277 return pruned; 278 } 279 280 /** 281 * Adds {@link AddeEntry} objects to a given {@link PatriciaTrie}. 282 * 283 * @param trie Cannot be {@code null}. 284 * @param newEntries Cannot be {@code null}. 285 */ 286 private static void putEntries( 287 final PatriciaTrie<AddeEntry> trie, 288 final Collection<? extends AddeEntry> newEntries) 289 { 290 requireNonNull(trie); 291 requireNonNull(newEntries); 292 293 for (AddeEntry e : newEntries) { 294 trie.put(e.asStringId(), e); 295 } 296 } 297 298 /** 299 * Returns the {@link IdvObjectStore} used to save user preferences. 300 * 301 * @return {@code IdvObjectStore} used by the rest of McIDAS-V. 302 */ 303 public IdvObjectStore getIdvStore() { 304 return idvStore; 305 } 306 307 /** 308 * Returns environment variables that allow mcservl to run on Windows. 309 * 310 * @return {@code String} array containing mcservl's environment variables. 311 */ 312 protected String[] getWindowsAddeEnv() { 313 // Drive letters should come from environment 314 // Java drive is not necessarily system drive 315 return new String[] { 316 "PATH=" + ADDE_BIN, 317 "MCPATH=" + USER_DIRECTORY+':'+ADDE_DATA, 318 "MCUSERDIR=" + USER_DIRECTORY, 319 "MCNOPREPEND=1", 320 "MCTRACE=" + (Boolean.parseBoolean(System.getProperty("debug.adde.reqs", "false")) ? "1" : "0"), 321 "MCTRACK=NO", 322 "MCJAVAPATH=" + System.getProperty("java.home"), 323 "MCBUFRJARPATH=" + ADDE_BIN, 324 "SYSTEMDRIVE=" + System.getenv("SystemDrive"), 325 "SYSTEMROOT=" + System.getenv("SystemRoot"), 326 "HOMEDRIVE=" + System.getenv("HOMEDRIVE"), 327 "HOMEPATH=\\Windows" 328 }; 329 } 330 331 /** 332 * Returns environment variables that allow mcservl to run on 333 * {@literal "unix-like"} systems. 334 * 335 * @return {@code String} array containing mcservl's environment variables. 336 */ 337 protected String[] getUnixAddeEnv() { 338 return new String[] { 339 "PATH=" + ADDE_BIN, 340 "HOME=" + System.getenv("HOME"), 341 "USER=" + System.getenv("USER"), 342 "MCPATH=" + USER_DIRECTORY+':'+ADDE_DATA, 343 "MCUSERDIR=" + USER_DIRECTORY, 344 "LD_LIBRARY_PATH=" + ADDE_BIN, 345 "DYLD_LIBRARY_PATH=" + ADDE_BIN, 346 "MCNOPREPEND=1", 347 "MCTRACE=" + (Boolean.parseBoolean(System.getProperty("debug.adde.reqs", "false")) ? "1" : "0"), 348 "MCTRACK=NO", 349 "MCJAVAPATH=" + System.getProperty("java.home"), 350 "MCBUFRJARPATH=" + ADDE_BIN 351 }; 352 } 353 354 /** 355 * Returns command line used to launch mcservl. 356 * 357 * @return {@code String} array that represents an invocation of mcservl. 358 */ 359 protected String[] getAddeCommands() { 360 String mcvPID = Integer.toString(PosixModule.getpid()); 361 if (McIDASV.isWindows() || (mcvPID == null) || "0".equals(mcvPID)) { 362 return new String[] { ADDE_MCSERVL, "-v", "-p", localPort }; 363 } else { 364 return new String[] { 365 ADDE_MCSERVL, "-v", "-p", localPort, "-i", mcvPID 366 }; 367 } 368 } 369 370 /** 371 * Determine the validity of a given {@link AddeEntry}. 372 * 373 * @param entry Entry to check. Cannot be {@code null}. 374 * 375 * @return {@code true} if {@code entry} is invalid or {@code false} 376 * otherwise. 377 * 378 * @throws NullPointerException if {@code entry} is {@code null}. 379 * @throws AssertionError if {@code entry} is somehow neither a 380 * {@code RemoteAddeEntry} or {@code LocalAddeEntry}. 381 * 382 * @see LocalAddeEntry#INVALID_ENTRY 383 * @see RemoteAddeEntry#INVALID_ENTRY 384 */ 385 public static boolean isInvalidEntry(final AddeEntry entry) { 386 requireNonNull(entry); 387 388 boolean retVal = true; 389 if (entry instanceof RemoteAddeEntry) { 390 retVal = RemoteAddeEntry.INVALID_ENTRY.equals(entry); 391 } else if (entry instanceof LocalAddeEntry) { 392 retVal = LocalAddeEntry.INVALID_ENTRY.equals(entry); 393 } else { 394 String clsName = entry.getClass().getName(); 395 throw new AssertionError("Unknown AddeEntry type: "+clsName); 396 } 397 return retVal; 398 } 399 400 /** 401 * Returns the {@link AddeEntry AddeEntries} stored in the user's 402 * preferences. 403 * 404 * @param store Object store that represents the user's preferences. 405 * Cannot be {@code null}. 406 * 407 * @return Either the {@code AddeEntrys} stored in the prefs or an empty 408 * {@link Set}. 409 */ 410 private Set<AddeEntry> extractPreferencesEntries( 411 final IdvObjectStore store) 412 { 413 assert store != null; 414 415 // this is valid--the only thing ever written to 416 // PREF_REMOTE_ADDE_ENTRIES is an ArrayList of RemoteAddeEntry objects. 417 @SuppressWarnings("unchecked") 418 List<AddeEntry> asList = 419 (List<AddeEntry>)store.get(PREF_ADDE_ENTRIES); 420 Set<AddeEntry> entries; 421 if (asList == null) { 422 entries = Collections.emptySet(); 423 } else { 424 entries = newLinkedHashSet(asList.size()); 425 entries.addAll( 426 asList.stream() 427 .filter(entry -> entry instanceof RemoteAddeEntry) 428 .collect(Collectors.toList())); 429 } 430 return entries; 431 } 432 433 /** 434 * Responds to server manager events being passed with the event bus. 435 * 436 * @param evt Event to which this method is responding. Cannot be 437 * {@code null}. 438 * 439 * @throws NullPointerException if {@code evt} is {@code null}. 440 */ 441 @EventSubscriber(eventClass=Event.class) 442 public void onEvent(Event evt) { 443 requireNonNull(evt); 444 445 saveEntries(); 446 } 447 448 /** 449 * Saves the current set of ADDE servers to the user's preferences and 450 * {@link #ADDE_RESOLV}. 451 */ 452 public void saveEntries() { 453 idvStore.put(PREF_ADDE_ENTRIES, arrList(trie.values())); 454 idvStore.saveIfNeeded(); 455 try { 456 EntryTransforms.writeResolvFile(ADDE_RESOLV, getLocalEntries()); 457 } catch (IOException e) { 458 logger.error(WARN_NO_RESOLVSRV, ADDE_RESOLV); 459 } 460 } 461 462 /** 463 * Saves the list of ADDE entries to both the user's preferences and 464 * {@link #ADDE_RESOLV}. 465 */ 466 public void saveForShutdown() { 467 idvStore.put(PREF_ADDE_ENTRIES, arrList(getPersistedEntrySet())); 468 idvStore.saveIfNeeded(); 469 try { 470 EntryTransforms.writeResolvFile(ADDE_RESOLV, 471 getPersistedLocalEntries()); 472 } catch (IOException e) { 473 logger.error(WARN_NO_RESOLVSRV, ADDE_RESOLV); 474 } 475 } 476 477 /** 478 * Searches the newest entries for the entries of the given 479 * {@link EntryType}. 480 * 481 * @param type Look for entries matching this {@code EntryType}. 482 * Cannot be {@code null}. 483 * 484 * @return Either a {@link List} of entries or an empty {@code List}. 485 * 486 * @throws NullPointerException if {@code type} is {@code null}. 487 */ 488 public List<AddeEntry> getLastAddedByType(final EntryType type) { 489 requireNonNull(type); 490 491 List<AddeEntry> entries = arrList(lastAdded.size()); 492 entries.addAll(lastAdded.stream() 493 .filter(entry -> entry.getEntryType() == type) 494 .collect(Collectors.toList())); 495 return entries; 496 } 497 498 /** 499 * Returns the {@link AddeEntry AddeEntries} that were added last, filtered 500 * by the given {@link EntryType EntryTypes}. 501 * 502 * @param types Filter the last added entries by these entry type. 503 * Cannot be {@code null}. 504 * 505 * @return {@link List} of the last added entries, filtered by 506 * {@code types}. 507 * 508 * @throws NullPointerException if {@code types} is {@code null}. 509 */ 510 public List<AddeEntry> getLastAddedByTypes(final EnumSet<EntryType> types) { 511 requireNonNull(types); 512 513 List<AddeEntry> entries = arrList(lastAdded.size()); 514 entries.addAll( 515 lastAdded.stream() 516 .filter(entry -> types.contains(entry.getEntryType())) 517 .collect(Collectors.toList())); 518 return entries; 519 } 520 521 /** 522 * Returns the {@link AddeEntry AddeEntries} that were added last. Note 523 * that this value is <b>not</b> preserved between sessions. 524 * 525 * @return {@link List} of the last ADDE entries that were added. May be 526 * empty. 527 */ 528 public List<AddeEntry> getLastAdded() { 529 return arrList(lastAdded); 530 } 531 532 /** 533 * Returns the {@link Set} of {@link AddeEntry AddeEntries} that are known 534 * to work (for a given {@link EntryType} of entries). 535 * 536 * @param type The {@code EntryType} you are interested in. Cannot be 537 * {@code null}. 538 * 539 * @return A {@code Set} of matching remote ADDE entries. If there were no 540 * matches, an empty {@code Set} is returned. 541 * 542 * @throws NullPointerException if {@code type} is {@code null}. 543 */ 544 public Set<AddeEntry> getVerifiedEntries(final EntryType type) { 545 requireNonNull(type); 546 547 Set<AddeEntry> verified = newLinkedHashSet(trie.size()); 548 for (AddeEntry entry : trie.values()) { 549 if (entry.getEntryType() != type) { 550 continue; 551 } 552 553 if (entry instanceof LocalAddeEntry) { 554 verified.add(entry); 555 } else if (entry.getEntryValidity() == EntryValidity.VERIFIED) { 556 verified.add(entry); 557 } 558 } 559 return verified; 560 } 561 562 /** 563 * Returns the available {@link AddeEntry AddeEntries}, grouped by 564 * {@link EntryType}. 565 * 566 * @return {@link Map} of {@code EntryType} to a {@link Set} containing all 567 * of the entries that match that {@code EntryType}. 568 */ 569 public Map<EntryType, Set<AddeEntry>> getVerifiedEntriesByTypes() { 570 Map<EntryType, Set<AddeEntry>> entryMap = 571 newLinkedHashMap(EntryType.values().length); 572 573 int size = trie.size(); 574 575 for (EntryType type : EntryType.values()) { 576 entryMap.put(type, new LinkedHashSet<>(size)); 577 } 578 579 for (AddeEntry entry : trie.values()) { 580 Set<AddeEntry> entrySet = entryMap.get(entry.getEntryType()); 581 entrySet.add(entry); 582 } 583 return entryMap; 584 } 585 586 /** 587 * Returns the {@link Set} of {@link AddeEntry#getGroup() groups} that 588 * match the given {@code address} and {@code type}. 589 * 590 * @param address ADDE server address whose groups are needed. 591 * Cannot be {@code null}. 592 * @param type Only include groups that match {@link EntryType}. 593 * Cannot be {@code null}. 594 * 595 * @return Either a set containing the desired groups, or an empty set if 596 * there were no matches. 597 * 598 * @throws NullPointerException if either {@code address} or {@code type} 599 * is {@code null}. 600 */ 601 public Set<String> getGroupsFor(final String address, EntryType type) { 602 requireNonNull(address); 603 requireNonNull(type); 604 605 Set<String> groups = newLinkedHashSet(trie.size()); 606 groups.addAll( 607 trie.prefixMap(address + '!').values().stream() 608 .filter(e -> e.getAddress().equals(address) && (e.getEntryType() == type)) 609 .map(AddeEntry::getGroup) 610 .collect(Collectors.toList())); 611 return groups; 612 } 613 614 /** 615 * Search the server manager for entries that match {@code prefix}. 616 * 617 * @param prefix {@code String} to match. Cannot be {@code null}. 618 * 619 * @return {@link List} containing matching entries. If there were no 620 * matches the {@code List} will be empty. 621 * 622 * @throws NullPointerException if {@code prefix} is {@code null}. 623 * 624 * @see AddeEntry#asStringId() 625 */ 626 public List<AddeEntry> searchWithPrefix(final String prefix) { 627 requireNonNull(prefix); 628 return arrList(trie.prefixMap(prefix).values()); 629 } 630 631 /** 632 * Returns the {@link Set} of {@link AddeEntry} addresses stored 633 * in this {@code EntryStore}. 634 * 635 * @return {@code Set} containing all of the stored addresses. If no 636 * addresses are stored, an empty {@code Set} is returned. 637 */ 638 public Set<String> getAddresses() { 639 Set<String> addresses = newLinkedHashSet(trie.size()); 640 addresses.addAll(trie.values().stream() 641 .map(AddeEntry::getAddress) 642 .collect(Collectors.toList())); 643 return addresses; 644 } 645 646 /** 647 * Returns a {@link Set} containing {@code ADDRESS/GROUPNAME} 648 * {@link String Strings} for each {@link RemoteAddeEntry}. 649 * 650 * @return The {@literal "entry text"} representations of each 651 * {@code RemoteAddeEntry}. 652 * 653 * @see RemoteAddeEntry#getEntryText() 654 */ 655 public Set<String> getRemoteEntryTexts() { 656 Set<String> strs = newLinkedHashSet(trie.size()); 657 strs.addAll(trie.values().stream() 658 .filter(entry -> entry instanceof RemoteAddeEntry) 659 .map(AddeEntry::getEntryText) 660 .collect(Collectors.toList())); 661 return strs; 662 } 663 664 /** 665 * Returns the {@link Set} of {@literal "groups"} associated with the 666 * given {@code address}. 667 * 668 * @param address Address of a server. 669 * 670 * @return Either all of the {@literal "groups"} on {@code address} or an 671 * empty {@code Set}. 672 */ 673 public Set<String> getGroups(final String address) { 674 requireNonNull(address); 675 676 Set<String> groups = newLinkedHashSet(trie.size()); 677 groups.addAll(trie.prefixMap(address + '!').values().stream() 678 .map(AddeEntry::getGroup) 679 .collect(Collectors.toList())); 680 return groups; 681 } 682 683 /** 684 * Returns the {@link Set} of {@link EntryType EntryTypes} for a given 685 * {@code group} on a given {@code address}. 686 * 687 * @param address Address of a server. 688 * @param group Group whose {@literal "types"} you want. 689 * 690 * @return Either of all the types for a given {@code address} and 691 * {@code group} or an empty {@code Set} if there were no matches. 692 */ 693 public Set<EntryType> getTypes(final String address, final String group) { 694 Set<EntryType> types = newLinkedHashSet(trie.size()); 695 types.addAll( 696 trie.prefixMap(address + '!' + group + '!').values().stream() 697 .map(AddeEntry::getEntryType) 698 .collect(Collectors.toList())); 699 return types; 700 } 701 702 /** 703 * Searches the set of servers in an attempt to locate the accounting 704 * information for the matching server. <b>Note</b> that because the data 705 * structure is a {@link Set}, there <i>cannot</i> be duplicate entries, 706 * so there is no need to worry about our criteria finding multiple 707 * matches. 708 * 709 * <p>Also note that none of the given parameters accept {@code null} 710 * values. 711 * 712 * @param address Address of the server. 713 * @param group Dataset. 714 * @param type Group type. 715 * 716 * @return Either the {@link AddeAccount} for the given criteria, or 717 * {@link AddeEntry#DEFAULT_ACCOUNT} if there was no match. 718 * 719 * @see RemoteAddeEntry#equals(Object) 720 */ 721 public AddeAccount getAccountingFor(final String address, 722 final String group, 723 EntryType type) 724 { 725 Collection<AddeEntry> entries = 726 trie.prefixMap(address+'!'+group+'!'+type.name()).values(); 727 for (AddeEntry entry : entries) { 728 if (!isInvalidEntry(entry)) { 729 return entry.getAccount(); 730 } 731 } 732 return AddeEntry.DEFAULT_ACCOUNT; 733 } 734 735 /** 736 * Returns the accounting for the given {@code idvServer} and 737 * {@code typeAsStr}. 738 * 739 * @param idvServer Server to search for. 740 * @param typeAsStr One of {@literal "IMAGE"}, {@literal "POINT"}, 741 * {@literal "GRID"}, {@literal "TEXT"}, {@literal "NAV"}, 742 * {@literal "RADAR"}, {@literal "UNKNOWN"}, or {@literal "INVALID"}. 743 * 744 * @return {@code AddeAccount} associated with {@code idvServer} and 745 * {@code typeAsStr}. 746 */ 747 public AddeAccount getAccountingFor(final AddeServer idvServer, 748 String typeAsStr) 749 { 750 String address = idvServer.getName(); 751 List<AddeServer.Group> groups = 752 (List<AddeServer.Group>)idvServer.getGroups(); 753 if ((groups != null) && !groups.isEmpty()) { 754 EntryType type = EntryTransforms.strToEntryType(typeAsStr); 755 return getAccountingFor(address, groups.get(0).getName(), type); 756 } else { 757 return RemoteAddeEntry.DEFAULT_ACCOUNT; 758 } 759 } 760 761 /** 762 * Returns the complete {@link Set} of {@link AddeEntry AddeEntries}. 763 * 764 * @return All of the managed ADDE entries. 765 */ 766 public Set<AddeEntry> getEntrySet() { 767 return newLinkedHashSet(trie.values()); 768 } 769 770 /** 771 * Returns all non-temporary {@link AddeEntry AddeEntries}. 772 * 773 * @return {@link Set} of ADDE entries that stick around between McIDAS-V 774 * sessions. 775 */ 776 public Set<AddeEntry> getPersistedEntrySet() { 777 Set<AddeEntry> entries = newLinkedHashSet(trie.size()); 778 entries.addAll(trie.values().stream() 779 .filter(entry -> !entry.isEntryTemporary()) 780 .collect(Collectors.toList())); 781 return entries; 782 } 783 784 /** 785 * Returns the complete {@link Set} of 786 * {@link RemoteAddeEntry RemoteAddeEntries}. 787 * 788 * @return {@code Set} of remote ADDE entries stored within the available 789 * entries. 790 */ 791 public Set<RemoteAddeEntry> getRemoteEntries() { 792 Set<RemoteAddeEntry> remotes = newLinkedHashSet(trie.size()); 793 remotes.addAll(trie.values().stream() 794 .filter(e -> e instanceof RemoteAddeEntry) 795 .map(e -> (RemoteAddeEntry) e) 796 .collect(Collectors.toList())); 797 return remotes; 798 } 799 800 /** 801 * Returns the complete {@link Set} of 802 * {@link LocalAddeEntry LocalAddeEntries}. 803 * 804 * @return {@code Set} of local ADDE entries stored within the available 805 * entries. 806 */ 807 public Set<LocalAddeEntry> getLocalEntries() { 808 Set<LocalAddeEntry> locals = newLinkedHashSet(trie.size()); 809 locals.addAll(trie.prefixMap("localhost").values().stream() 810 .filter(e -> e instanceof LocalAddeEntry) 811 .map(e -> (LocalAddeEntry) e) 812 .collect(Collectors.toList())); 813 return locals; 814 } 815 816 /** 817 * Returns the {@link Set} of {@link LocalAddeEntry LocalAddeEntries} that 818 * will be saved between McIDAS-V sessions. 819 * 820 * <p>Note: all this does is check {@link LocalAddeEntry#isEntryTemporary()}. 821 * 822 * @return Local ADDE entries that will be saved for the next session. 823 */ 824 public Set<LocalAddeEntry> getPersistedLocalEntries() { 825// Set<LocalAddeEntry> locals = newLinkedHashSet(trie.size()); 826// for (AddeEntry e : trie.getPrefixedBy("localhost").values()) { 827// if (e instanceof LocalAddeEntry) { 828// LocalAddeEntry local = (LocalAddeEntry)e; 829// if (!local.isEntryTemporary()) { 830// locals.add(local); 831// } 832// } 833// } 834// return locals; 835 return this.filterLocalEntriesByTemporaryStatus(false); 836 } 837 838 /** 839 * Returns any {@link LocalAddeEntry LocalAddeEntries} that will be removed 840 * at the end of the current McIDAS-V session. 841 * 842 * @return {@code Set} of all the temporary local ADDE entries. 843 */ 844 public Set<LocalAddeEntry> getTemporaryLocalEntries() { 845 return this.filterLocalEntriesByTemporaryStatus(true); 846 } 847 848 /** 849 * Filters the local entries by whether or not they are set as 850 * {@literal "temporary"}. 851 * 852 * @param getTemporaryEntries {@code true} returns temporary local 853 * entries; {@code false} returns local entries that are permanent. 854 * 855 * @return {@link Set} of filtered local ADDE entries. 856 */ 857 private Set<LocalAddeEntry> filterLocalEntriesByTemporaryStatus( 858 final boolean getTemporaryEntries) 859 { 860 Set<LocalAddeEntry> locals = newLinkedHashSet(trie.size()); 861 trie.prefixMap("localhost").values().stream() 862 .filter(e -> e instanceof LocalAddeEntry) 863 .forEach(e -> { 864 LocalAddeEntry local = (LocalAddeEntry)e; 865 if (local.isEntryTemporary() == getTemporaryEntries) { 866 locals.add(local); 867 } 868 }); 869 return locals; 870 } 871 872 /** 873 * Removes the given {@link AddeEntry AddeEntries}. 874 * 875 * @param removedEntries {@code AddeEntry} objects to remove. 876 * Cannot be {@code null}. 877 * 878 * @return Whether or not {@code removeEntries} were removed. 879 * 880 * @throws NullPointerException if {@code removedEntries} is {@code null}. 881 */ 882 public boolean removeEntries( 883 final Collection<? extends AddeEntry> removedEntries) 884 { 885 requireNonNull(removedEntries); 886 887 boolean val = true; 888 boolean tmp = true; 889 for (AddeEntry e : removedEntries) { 890 if (e.getEntrySource() != EntrySource.SYSTEM) { 891 tmp = trie.remove(e.asStringId()) != null; 892 logger.trace("attempted bulk remove={} status={}", e, tmp); 893 if (!tmp) { 894 val = tmp; 895 } 896 } 897 } 898 Event evt = val ? Event.REMOVAL : Event.FAILURE; 899 saveEntries(); 900 EventBus.publish(evt); 901 return val; 902 } 903 904 /** 905 * Removes a single {@link AddeEntry} from the set of available entries. 906 * 907 * @param entry Entry to remove. Cannot be {@code null}. 908 * 909 * @return {@code true} if something was removed, {@code false} otherwise. 910 * 911 * @throws NullPointerException if {@code entry} is {@code null}. 912 */ 913 public boolean removeEntry(final AddeEntry entry) { 914 requireNonNull(entry); 915 916 boolean val = trie.remove(entry.asStringId()) != null; 917 logger.trace("attempted remove={} status={}", entry, val); 918 Event evt = val ? Event.REMOVAL : Event.FAILURE; 919 saveEntries(); 920 EventBus.publish(evt); 921 return val; 922 } 923 924 /** 925 * Adds a single {@link AddeEntry} to {@link #trie}. 926 * 927 * @param entry Entry to add. Cannot be {@code null}. 928 * 929 * @throws NullPointerException if {@code entry} is {@code null}. 930 */ 931 public void addEntry(final AddeEntry entry) { 932 requireNonNull(entry, "Cannot add a null entry."); 933 934 trie.put(entry.asStringId(), entry); 935 saveEntries(); 936 lastAdded.clear(); 937 lastAdded.add(entry); 938 EventBus.publish(Event.ADDITION); 939 } 940 941 /** 942 * Adds a {@link Set} of {@link AddeEntry AddeEntries} to {@link #trie}. 943 * 944 * @param newEntries New entries to add to the server manager. Cannot be 945 * {@code null}. 946 * 947 * @throws NullPointerException if {@code newEntries} is {@code null}. 948 */ 949 public void addEntries(final Collection<? extends AddeEntry> newEntries) { 950 requireNonNull(newEntries, "Cannot add a null Collection."); 951 952 for (AddeEntry newEntry : newEntries) { 953 trie.put(newEntry.asStringId(), newEntry); 954 } 955 saveEntries(); 956 lastAdded.clear(); 957 lastAdded.addAll(newEntries); 958 EventBus.publish(Event.ADDITION); 959 } 960 961 /** 962 * Replaces the {@link AddeEntry AddeEntries} within {@code trie} with the 963 * contents of {@code newEntries}. 964 * 965 * @param oldEntries Entries to be replaced. Cannot be {@code null}. 966 * @param newEntries Entries to use as replacements. Cannot be 967 * {@code null}. 968 * 969 * @throws NullPointerException if either of {@code oldEntries} or 970 * {@code newEntries} is {@code null}. 971 */ 972 public void replaceEntries( 973 final Collection<? extends AddeEntry> oldEntries, 974 final Collection<? extends AddeEntry> newEntries) 975 { 976 requireNonNull(oldEntries, "Cannot replace a null Collection."); 977 requireNonNull(newEntries, "Cannot add a null Collection."); 978 979 for (AddeEntry oldEntry : oldEntries) { 980 trie.remove(oldEntry.asStringId()); 981 } 982 for (AddeEntry newEntry : newEntries) { 983 trie.put(newEntry.asStringId(), newEntry); 984 } 985 lastAdded.clear(); 986 lastAdded.addAll(newEntries); // should probably be more thorough 987 saveEntries(); 988 EventBus.publish(Event.REPLACEMENT); 989 } 990 991 /** 992 * Returns all enabled, valid {@link LocalAddeEntry LocalAddeEntries} as a 993 * collection of {@literal "IDV style"} {@link AddeServer.Group} 994 * objects. 995 * 996 * @return {@link Set} of {@code AddeServer.Group} objects that corresponds 997 * with the enabled, valid local ADDE entries. 998 */ 999 // if true, filters out disabled local groups; if false, returns all 1000 // local groups 1001 public Set<AddeServer.Group> getIdvStyleLocalGroups() { 1002 Set<LocalAddeEntry> localEntries = getLocalEntries(); 1003 Set<AddeServer.Group> idvGroups = newLinkedHashSet(localEntries.size()); 1004 for (LocalAddeEntry e : localEntries) { 1005 boolean enabled = e.getEntryStatus() == EntryStatus.ENABLED; 1006 boolean verified = e.getEntryValidity() == EntryValidity.VERIFIED; 1007 if (enabled && verified) { 1008 String group = e.getGroup(); 1009 AddeServer.Group idvGroup = 1010 new AddeServer.Group("IMAGE", group, group); 1011 idvGroups.add(idvGroup); 1012 } 1013 } 1014 return idvGroups; 1015 } 1016 1017 /** 1018 * Returns the entries matching the given {@code server} and 1019 * {@code typeAsStr} parameters as a collection of 1020 * {@link ucar.unidata.idv.chooser.adde.AddeServer.Group AddeServer.Group} 1021 * objects. 1022 * 1023 * @param server Remote ADDE server. Should not be {@code null}. 1024 * @param typeAsStr Entry type. One of {@literal "IMAGE"}, 1025 * {@literal "POINT"}, {@literal "GRID"}, {@literal "TEXT"}, 1026 * {@literal "NAV"}, {@literal "RADAR"}, {@literal "UNKNOWN"}, or 1027 * {@literal "INVALID"}. Should not be {@code null}. 1028 * 1029 * @return {@link Set} of {@code AddeServer.Group} objects that corresponds 1030 * to the entries associated with {@code server} and {@code typeAsStr}. 1031 */ 1032 public Set<AddeServer.Group> getIdvStyleRemoteGroups( 1033 final String server, 1034 final String typeAsStr) 1035 { 1036 return getIdvStyleRemoteGroups(server, 1037 EntryTransforms.strToEntryType(typeAsStr)); 1038 } 1039 1040 /** 1041 * Returns the entries matching the given {@code server} and 1042 * {@code type} parameters as a collection of 1043 * {@link AddeServer.Group} 1044 * objects. 1045 * 1046 * @param server Remote ADDE server. Should not be {@code null}. 1047 * @param type Entry type. Should not be {@code null}. 1048 * 1049 * @return {@link Set} of {@code AddeServer.Group} objects that corresponds 1050 * to the entries associated with {@code server} and {@code type}. 1051 */ 1052 public Set<AddeServer.Group> getIdvStyleRemoteGroups(final String server, 1053 final EntryType type) 1054 { 1055 Set<AddeServer.Group> idvGroups = newLinkedHashSet(trie.size()); 1056 String typeStr = type.name(); 1057 for (AddeEntry e : trie.prefixMap(server).values()) { 1058 if (e == RemoteAddeEntry.INVALID_ENTRY) { 1059 continue; 1060 } 1061 1062 boolean enabled = e.getEntryStatus() == EntryStatus.ENABLED; 1063 boolean verified = e.getEntryValidity() == EntryValidity.VERIFIED; 1064 boolean typeMatched = e.getEntryType() == type; 1065 if (enabled && verified && typeMatched) { 1066 String group = e.getGroup(); 1067 idvGroups.add(new AddeServer.Group(typeStr, group, group)); 1068 } 1069 } 1070 return idvGroups; 1071 } 1072 1073 /** 1074 * Returns a list of all available ADDE datasets, converted to IDV 1075 * {@link AddeServer} objects. 1076 * 1077 * @return List of {@code AddeServer} objects for each ADDE entry. 1078 */ 1079 public List<AddeServer> getIdvStyleEntries() { 1080 return arrList(EntryTransforms.convertMcvServers(getEntrySet())); 1081 } 1082 1083 /** 1084 * Returns a list that consists of the available ADDE datasets for a given 1085 * {@link EntryType}, converted to IDV {@link AddeServer} objects. 1086 * 1087 * @param type Only add entries with this type to the returned list. 1088 * Cannot be {@code null}. 1089 * 1090 * @return {@code AddeServer} objects for each ADDE entry of the given type. 1091 */ 1092 public Set<AddeServer> getIdvStyleEntries(final EntryType type) { 1093 return EntryTransforms.convertMcvServers(getVerifiedEntries(type)); 1094 } 1095 1096 /** 1097 * Returns a list that consists of the available ADDE datasets for a given 1098 * {@link EntryType}, converted to IDV {@link AddeServer} objects. 1099 * 1100 * @param typeAsStr Only add entries with this type to the returned list. 1101 * Cannot be {@code null} and must be a value that works with 1102 * {@link EntryTransforms#strToEntryType(String)}. 1103 * 1104 * @return {@code AddeServer} objects for each ADDE entry of the given type. 1105 * 1106 * @see EntryTransforms#strToEntryType(String) 1107 */ 1108 public Set<AddeServer> getIdvStyleEntries(final String typeAsStr) { 1109 return getIdvStyleEntries(EntryTransforms.strToEntryType(typeAsStr)); 1110 } 1111 1112 /** 1113 * Process all of the {@literal "IDV-style"} XML resources for a given 1114 * {@literal "source"}. 1115 * 1116 * @param source Origin of the XML resources. 1117 * @param xmlResources Actual XML resources. 1118 * 1119 * @return {@link Set} of the {@link AddeEntry AddeEntrys} extracted from 1120 * {@code xmlResources}. 1121 */ 1122 private Set<AddeEntry> extractResourceEntries( 1123 EntrySource source, 1124 final XmlResourceCollection xmlResources) 1125 { 1126 Set<AddeEntry> entries = newLinkedHashSet(xmlResources.size()); 1127 for (int i = 0; i < xmlResources.size(); i++) { 1128 Element root = xmlResources.getRoot(i); 1129 if (root == null) { 1130 continue; 1131 } 1132 entries.addAll(EntryTransforms.convertAddeServerXml(root, source)); 1133 } 1134 return entries; 1135 } 1136 1137 /** 1138 * Process all of the {@literal "user"} XML resources. 1139 * 1140 * @param xmlResources Resource collection. Cannot be {@code null}. 1141 * 1142 * @return {@link Set} of {@link RemoteAddeEntry RemoteAddeEntries} 1143 * contained within {@code resource}. 1144 */ 1145 private Set<AddeEntry> extractUserEntries( 1146 final XmlResourceCollection xmlResources) 1147 { 1148 int rcSize = xmlResources.size(); 1149 Set<AddeEntry> entries = newLinkedHashSet(rcSize); 1150 for (int i = 0; i < rcSize; i++) { 1151 Element root = xmlResources.getRoot(i); 1152 if (root == null) { 1153 continue; 1154 } 1155 entries.addAll(EntryTransforms.convertUserXml(root)); 1156 } 1157 return entries; 1158 } 1159 1160 /** 1161 * Returns the path to where the root directory of the user's McIDAS-X 1162 * binaries <b>should</b> be. <b>The path may be invalid.</b> 1163 * 1164 * <p>The default path is determined like so: 1165 * <pre> 1166 * System.getProperty("user.dir") + File.separatorChar + "adde" 1167 * </pre> 1168 * 1169 * <p>Users can provide an arbitrary path at runtime by setting the 1170 * {@code debug.localadde.rootdir} system property. 1171 * 1172 * @return {@code String} containing the path to the McIDAS-X root 1173 * directory. 1174 * 1175 * @see #PROP_DEBUG_LOCALROOT 1176 */ 1177 public static String getAddeRootDirectory() { 1178 if (System.getProperties().containsKey(PROP_DEBUG_LOCALROOT)) { 1179 return System.getProperty(PROP_DEBUG_LOCALROOT); 1180 } 1181 String userDir = System.getProperty("user.dir"); 1182 Path p; 1183 if (userDir.endsWith("lib") || userDir.endsWith("lib/")) { 1184 p = Paths.get(userDir, "..", "adde"); 1185 } else { 1186 p = Paths.get(userDir, "adde"); 1187 } 1188 return p.normalize().toString(); 1189 } 1190 1191 /** 1192 * Checks the value of the {@code debug.adde.reqs} system property to 1193 * determine whether or not the user has requested ADDE URL debugging 1194 * output. Output is sent to {@link System#out}. 1195 * 1196 * <p>Please keep in mind that the {@code debug.adde.reqs} can not 1197 * force debugging for <i>all</i> ADDE requests. To do so will require 1198 * updates to the VisAD ADDE library. 1199 * 1200 * @param defValue Value to return if {@code debug.adde.reqs} has 1201 * not been set. 1202 * 1203 * @return If it exists, the value of {@code debug.adde.reqs}. 1204 * Otherwise {@code debug.adde.reqs}. 1205 * 1206 * @see edu.wisc.ssec.mcidas.adde.AddeURL 1207 * @see #PROP_DEBUG_ADDEURL 1208 */ 1209 // TODO(jon): this sort of thing should *really* be happening within the 1210 // ADDE library. 1211 public static boolean isAddeDebugEnabled(final boolean defValue) { 1212 String systemProperty = 1213 System.getProperty(PROP_DEBUG_ADDEURL, Boolean.toString(defValue)); 1214 return Boolean.parseBoolean(systemProperty); 1215 } 1216 1217 /** 1218 * Sets the value of the {@code debug.adde.reqs} system property so 1219 * that debugging output can be controlled without restarting McIDAS-V. 1220 * 1221 * <p>Please keep in mind that the {@code debug.adde.reqs} can not 1222 * force debugging for <i>all</i> ADDE requests. To do so will require 1223 * updates to the VisAD ADDE library. 1224 * 1225 * @param value New value of {@code debug.adde.reqs}. 1226 * 1227 * @return Previous value of {@code debug.adde.reqs}. 1228 * 1229 * @see edu.wisc.ssec.mcidas.adde.AddeURL 1230 * @see #PROP_DEBUG_ADDEURL 1231 */ 1232 public static boolean setAddeDebugEnabled(final boolean value) { 1233 String systemProperty = 1234 System.setProperty(PROP_DEBUG_ADDEURL, Boolean.toString(value)); 1235 return Boolean.parseBoolean(systemProperty); 1236 } 1237 1238 /** 1239 * Change the port we are listening on. 1240 * 1241 * @param port New port number. 1242 */ 1243 public static void setLocalPort(final String port) { 1244 localPort = port; 1245 } 1246 1247 /** 1248 * Ask for the port we are listening on. 1249 * 1250 * @return String representation of the listening port. 1251 */ 1252 public static String getLocalPort() { 1253 return localPort; 1254 } 1255 1256 /** 1257 * Get the next port by incrementing current port. 1258 * 1259 * @return The next port that will be tried. 1260 */ 1261 protected static String nextLocalPort() { 1262 return Integer.toString(Integer.parseInt(localPort) + 1); 1263 } 1264 1265 /** 1266 * Starts the local server thread (if it isn't already running). 1267 */ 1268 public void startLocalServer() { 1269 if (new File(ADDE_MCSERVL).exists()) { 1270 // Create and start the thread if there isn't already one running 1271 if (!checkLocalServer()) { 1272 if (!testLocalServer()) { 1273 String logUtil = 1274 String.format(ERROR_LOGUTIL_USERPATH, USER_DIRECTORY); 1275 LogUtil.userErrorMessage(logUtil); 1276 logger.info(ERROR_USERPATH, USER_DIRECTORY); 1277 return; 1278 } 1279 thread = new AddeThread(this); 1280 thread.start(); 1281 EventBus.publish(McservEvent.STARTED); 1282 boolean status = checkLocalServer(); 1283 logger.debug("started mcservl? checkLocalServer={}", status); 1284 } else { 1285 logger.debug("mcservl is already running"); 1286 } 1287 } else { 1288 logger.debug("invalid path='{}'", ADDE_MCSERVL); 1289 } 1290 } 1291 1292 /** 1293 * Stops the local server thread if it is running. 1294 */ 1295 public void stopLocalServer() { 1296 if (checkLocalServer()) { 1297 //TODO: stopProcess (actually Process.destroy()) hangs on Macs... 1298 // doesn't seem to kill the children properly 1299 if (!McIDASV.isMac()) { 1300 thread.stopProcess(); 1301 } 1302 1303 thread.interrupt(); 1304 thread = null; 1305 EventBus.publish(McservEvent.STOPPED); 1306 boolean status = checkLocalServer(); 1307 logger.debug("stopped mcservl? checkLocalServer={}", status); 1308 } else { 1309 logger.debug("mcservl is not running."); 1310 } 1311 } 1312 1313 /** 1314 * Test to see if the thread can access userpath 1315 * 1316 * @return {@code true} if the local server can access userpath, 1317 * {@code false} otherwise. 1318 */ 1319 public boolean testLocalServer() { 1320 String[] cmds = { ADDE_MCSERVL, "-t" }; 1321 String[] env = McIDASV.isWindows() 1322 ? getWindowsAddeEnv() 1323 : getUnixAddeEnv(); 1324 1325 try { 1326 Process proc = Runtime.getRuntime().exec(cmds, env); 1327 int result = proc.waitFor(); 1328 if (result != 0) { 1329 return false; 1330 } 1331 } catch (Exception e) { 1332 return false; 1333 } 1334 return true; 1335 } 1336 1337 /** 1338 * Check to see if the thread is running. 1339 * 1340 * @return {@code true} if the local server thread is running; 1341 * {@code false} otherwise. 1342 */ 1343 public boolean checkLocalServer() { 1344 return (thread != null) && thread.isAlive(); 1345 } 1346}