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 }