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