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