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