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