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 }