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