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