package org.yamcs.xtce; import java.io.PrintStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yamcs.protobuf.Yamcs.NamedObjectId; import org.yamcs.xtce.xml.XtceAliasSet; /** *XtceDB database * * It contains a SpaceSystem as defined in the Xtce schema and has lots of hashes to help find things quickly * * @author mache */ public class XtceDb implements Serializable { private static final long serialVersionUID = 54L; SpaceSystem rootSystem; //rwLock is used to guard the read/write of parameters and spaceSystems which are the only ones that can change dynamically as of now ReadWriteLock rwLock = new ReentrantReadWriteLock(); public XtceDb(SpaceSystem spaceSystem) { this.rootSystem=spaceSystem; } /** * Namespaces system parameters */ public static final String YAMCS_SPACESYSTEM_NAME = "/yamcs"; public static final String YAMCS_CMD_SPACESYSTEM_NAME = "/yamcs/cmd"; public static final String YAMCS_CMDHIST_SPACESYSTEM_NAME = "/yamcs/cmdHist"; transient static Logger log = LoggerFactory.getLogger(XtceDb.class); //map from the fully qualified names to the objects private HashMap<String, SpaceSystem> spaceSystems = new HashMap<>(); private Map<String, SequenceContainer> sequenceContainers = new LinkedHashMap<>(); private Map<String, Parameter> parameters = new LinkedHashMap<>(); private HashMap<String, Algorithm> algorithms = new HashMap<>(); private HashMap<String, MetaCommand> commands = new HashMap<>(); @SuppressWarnings("rawtypes") private HashMap<Class<?>, NonStandardData> nonStandardDatas = new HashMap<>(); //different namespaces private NamedDescriptionIndex<SpaceSystem> spaceSystemAliases = new NamedDescriptionIndex<>(); private NamedDescriptionIndex<Parameter> parameterAliases = new NamedDescriptionIndex<>(); private NamedDescriptionIndex<SequenceContainer> sequenceContainerAliases =new NamedDescriptionIndex<>(); private NamedDescriptionIndex<Algorithm> algorithmAliases = new NamedDescriptionIndex<>(); private NamedDescriptionIndex<MetaCommand> commandAliases = new NamedDescriptionIndex<>(); private Set<String> namespaces = new HashSet<>(); //this is the sequence container where the xtce processors start processing //we should perhaps have possibility to specify different ones for different streams SequenceContainer rootSequenceContainer; /** * Maps the Parameter to a list of ParameterEntry such that we know from * which container we can extract this parameter */ private HashMap<Parameter, ArrayList<ParameterEntry>> parameter2ParameterEntryMap; /** * maps the SequenceContainer to a list of other EntryContainers in case of * aggregation */ private HashMap<SequenceContainer, ArrayList<ContainerEntry>> sequenceContainer2ContainerEntryMap; /** * maps the SequenceContainer to a list of containers inheriting this one */ private HashMap<SequenceContainer, ArrayList<SequenceContainer>> sequenceContainer2InheritingContainerMap; public SequenceContainer getSequenceContainer(String qualifiedName) { return sequenceContainers.get(qualifiedName); } public SequenceContainer getSequenceContainer(String namespace, String name) { return sequenceContainerAliases.get(namespace, name); } public SequenceContainer getSequenceContainer(NamedObjectId id) { if(id.hasNamespace()) { return sequenceContainerAliases.get(id.getNamespace(), id.getName()); } else { return sequenceContainerAliases.get(id.getName()); } } public Parameter getParameter(String qualifiedName) { rwLock.readLock().lock(); try { return parameters.get(qualifiedName); } finally { rwLock.readLock().unlock(); } } public Parameter getParameter(String namespace, String name) { rwLock.readLock().lock(); try { return parameterAliases.get(namespace,name); } finally { rwLock.readLock().unlock(); } } public Parameter getParameter(NamedObjectId id) { rwLock.readLock().lock(); try { if (id.hasNamespace()) { return parameterAliases.get(id.getNamespace(), id.getName()); } else { return parameterAliases.get(id.getName()); } } finally { rwLock.readLock().unlock(); } } public SequenceContainer getRootSequenceContainer() { return rootSequenceContainer; } public void setRootSequenceContainer(SequenceContainer sc) { this.rootSequenceContainer = sc; } public Algorithm getAlgorithm(String qualifiedName) { return algorithmAliases.get(qualifiedName); } public Algorithm getAlgorithm(String namespace, String name) { return algorithmAliases.get(namespace, name); } public Algorithm getAlgorithm(NamedObjectId id) { if (id.hasNamespace()) { return algorithmAliases.get(id.getNamespace(), id.getName()); } else { return algorithmAliases.get(id.getName()); } } public Collection<Algorithm> getAlgorithms() { return algorithms.values(); } public Collection<Parameter> getParameters() { rwLock.readLock().lock(); try { return parameters.values(); } finally { rwLock.readLock().unlock(); } } public boolean containsNamespace(String namespace) { return namespaces.contains(namespace); } public Set<String> getNamespaces() { return namespaces; } public MetaCommand getMetaCommand(String qualifiedName) { rwLock.readLock().lock(); try { return commandAliases.get(qualifiedName); } finally { rwLock.readLock().unlock(); } } /** * Returns a command based on a name in a namespace * @param namespace * @param name * @return */ public MetaCommand getMetaCommand(String namespace, String name) { rwLock.readLock().lock(); try { return commandAliases.get(namespace, name); } finally { rwLock.readLock().unlock(); } } public MetaCommand getMetaCommand(NamedObjectId id) { rwLock.readLock().lock(); try { if (id.hasNamespace()) { return commandAliases.get(id.getNamespace(), id.getName()); } else { return commandAliases.get(id.getName()); } } finally { rwLock.readLock().unlock(); } } /** * Returns the list of MetaCommmands in the XTCE database * @return */ public Collection<MetaCommand> getMetaCommands() { rwLock.readLock().lock(); try { return commands.values(); } finally { rwLock.readLock().unlock(); } } public SpaceSystem getRootSpaceSystem() { return rootSystem; } public SpaceSystem getSpaceSystem(String qualifiedName) { rwLock.readLock().lock(); try { return spaceSystemAliases.get(qualifiedName); } finally { rwLock.readLock().unlock(); } } public SpaceSystem getSpaceSystem(String namespace, String name) { rwLock.readLock().lock(); try { return spaceSystemAliases.get(namespace, name); } finally { rwLock.readLock().unlock(); } } public SpaceSystem getSpaceSystem(NamedObjectId id) { rwLock.readLock().lock(); try { if (id.hasNamespace()) { return spaceSystemAliases.get(id.getNamespace(), id.getName()); } else { return spaceSystemAliases.get(id.getName()); } } finally { rwLock.readLock().unlock(); } } public Collection<SequenceContainer> getSequenceContainers() { return sequenceContainers.values(); } /** * * @return list of ParameterEntry corresponding to a given parameter. */ public List<ParameterEntry> getParameterEntries(Parameter p) { return parameter2ParameterEntryMap.get(p); } /** * @return list of ContainerEntry corresponding to a given sequence * container. */ public List<ContainerEntry> getContainerEntries(SequenceContainer sc) { return sequenceContainer2ContainerEntryMap.get(sc); } public Collection<String> getParameterNames() { return parameters.keySet(); } @SuppressWarnings("unchecked") public <T extends NonStandardData<T>> T getNonStandardDataOfType(Class<T> clazz) { if(nonStandardDatas.containsKey(clazz)) return (T) nonStandardDatas.get(clazz); else return null; } @SuppressWarnings("rawtypes") public Collection<NonStandardData> getNonStandardData() { return nonStandardDatas.values(); } /** * Called after the database has been populated to build the maps for * quickly finding things * */ public void buildIndexMaps() { buildSpaceSystemsMap(rootSystem) ; buildParameterMap(rootSystem); buildSequenceContainerMap(rootSystem); buildAlgorithmMap(rootSystem); buildMetaCommandMap(rootSystem); buildNonStandardDataMap(rootSystem); parameter2ParameterEntryMap = new HashMap<Parameter, ArrayList<ParameterEntry>>(); sequenceContainer2ContainerEntryMap = new HashMap<SequenceContainer, ArrayList<ContainerEntry>>(); sequenceContainer2InheritingContainerMap = new HashMap<SequenceContainer, ArrayList<SequenceContainer>>(); for (SequenceContainer sc : sequenceContainers.values()) { for (SequenceEntry se : sc.getEntryList()) { if (se instanceof ParameterEntry) { ParameterEntry pe = (ParameterEntry) se; Parameter param = pe.getParameter(); ArrayList<ParameterEntry> al = parameter2ParameterEntryMap.get(param); if (al == null) { al = new ArrayList<ParameterEntry>(); parameter2ParameterEntryMap.put(param, al); } al.add(pe); } else if (se instanceof ContainerEntry) { ContainerEntry ce = (ContainerEntry) se; ArrayList<ContainerEntry> al = sequenceContainer2ContainerEntryMap .get(ce.getRefContainer()); if (al == null) { al = new ArrayList<ContainerEntry>(); sequenceContainer2ContainerEntryMap.put(ce.getRefContainer(), al); } al.add(ce); } } if (sc.baseContainer != null) { ArrayList<SequenceContainer> al_sc = sequenceContainer2InheritingContainerMap .get(sc.baseContainer); if (al_sc == null) { al_sc = new ArrayList<SequenceContainer>(); sequenceContainer2InheritingContainerMap.put(sc.baseContainer, al_sc); } al_sc.add(sc); } } //build aliases maps for (SpaceSystem ss : spaceSystems.values()) { spaceSystemAliases.add(ss); XtceAliasSet aliases = ss.getAliasSet(); if(aliases!=null) { aliases.getNamespaces().forEach(ns -> namespaces.add(ns)); } } for (SequenceContainer sc : sequenceContainers.values()) { sequenceContainerAliases.add(sc); XtceAliasSet aliases = sc.getAliasSet(); if(aliases!=null) { aliases.getNamespaces().forEach(ns -> namespaces.add(ns)); } } for(Parameter p:parameters.values()) { parameterAliases.add(p); XtceAliasSet aliases=p.getAliasSet(); if(aliases!=null) { aliases.getNamespaces().forEach(ns -> namespaces.add(ns)); } } for(Algorithm a:algorithms.values()) { algorithmAliases.add(a); XtceAliasSet aliases=a.getAliasSet(); if(aliases!=null) { aliases.getNamespaces().forEach(ns -> namespaces.add(ns)); } } for(MetaCommand mc:commands.values()) { commandAliases.add(mc); XtceAliasSet aliases=mc.getAliasSet(); if(aliases!=null) { aliases.getNamespaces().forEach(ns -> namespaces.add(ns)); } } } private void buildSpaceSystemsMap(SpaceSystem ss) { spaceSystems.put(ss.getQualifiedName(), ss); for(SpaceSystem ss1:ss.getSubSystems()) { buildSpaceSystemsMap(ss1); } } private void buildParameterMap(SpaceSystem ss) { for(Parameter p:ss.getParameters()) { parameters.put(p.getQualifiedName(), p); } for(SpaceSystem ss1:ss.getSubSystems()) { buildParameterMap(ss1); } } private void buildSequenceContainerMap(SpaceSystem ss) { for(SequenceContainer sc:ss.getSequenceContainers()) { sequenceContainers.put(sc.getQualifiedName(), sc); } for(SpaceSystem ss1:ss.getSubSystems()) { buildSequenceContainerMap(ss1); } } private void buildAlgorithmMap(SpaceSystem ss) { for(Algorithm a:ss.getAlgorithms()) { algorithms.put(a.getQualifiedName(), a); } for(SpaceSystem ss1:ss.getSubSystems()) { buildAlgorithmMap(ss1); } } private void buildMetaCommandMap(SpaceSystem ss) { for(MetaCommand mc:ss.getMetaCommands()) { commands.put(mc.getQualifiedName(), mc); } for(SpaceSystem ss1:ss.getSubSystems()) { buildMetaCommandMap(ss1); } } @SuppressWarnings({"rawtypes", "unchecked"}) private void buildNonStandardDataMap(SpaceSystem ss) { for(NonStandardData data:ss.getNonStandardData()) { if(nonStandardDatas.containsKey(data.getClass())) { NonStandardData mergeResult=nonStandardDatas.get(data.getClass()).mergeWithChild(data); nonStandardDatas.put(data.getClass(), mergeResult); } else { nonStandardDatas.put(data.getClass(), data); } } for(SpaceSystem ss1:ss.getSubSystems()) { buildNonStandardDataMap(ss1); } } public List<SequenceContainer> getInheritingContainers(SequenceContainer container) { return sequenceContainer2InheritingContainerMap.get(container); } /** * Adds a new parameter to the XTCE db. * * * If the SpaceSystem where this parameter belongs does not exist, and createSpaceSystem is false, throws an IllegalArgumentException * If the SpaceSystem where this parameter belongs exists and already contains an parameter by this name, throws and IllegalArgumentException * If the SpaceSystem where this parameter belongs does not exist, and createSpaceSystem is true, the whole SpaceSystem hierarchy is created. * * Note that this method is used to create parameters on the fly. * The parameters are not saved anywhere and they will not be available when this object is created by the XtceDbFactory. * * @param p * @param createSpaceSystem - if true, create all the necessary space systems */ public void addParameter(Parameter p, boolean createSpaceSystem) { rwLock.writeLock().lock(); try { String ssname = p.getSubsystemName(); SpaceSystem ss = spaceSystems.get(ssname); if(ss==null) { if(!createSpaceSystem) { throw new IllegalArgumentException("No SpaceSystem by name '"+ssname+"'"); } else { createAllSpaceSystems(ssname); } } ss = spaceSystems.get(ssname); ss.addParameter(p); parameters.put(p.getQualifiedName(), p); parameterAliases.add(p); XtceAliasSet aliases=p.getAliasSet(); if(aliases!=null) { aliases.getNamespaces().forEach(ns -> namespaces.add(ns)); } } finally { rwLock.writeLock().unlock(); } } private void createAllSpaceSystems(String ssname) { String[] a = ssname.split("/"); String qn = ""; for(String name:a) { if(name.isEmpty()) continue; qn = qn+"/"+name; if(getSpaceSystem(qn) == null) { SpaceSystem ss = new SpaceSystem(name); ss.setQualifiedName(qn); addSpaceSystem(ss); } } } /** * Adds a new command definition to the XTCE db. * <p> * Note that this method is used to create commands on the fly.<br> * The commands are not saved anywhere and they will not be available when * this object is created with the XtceDbFactory. */ public void addMetaCommand(MetaCommand c) { rwLock.writeLock().lock(); try { String ssname = c.getSubsystemName(); SpaceSystem ss = spaceSystems.get(ssname); if (ss == null) { throw new IllegalArgumentException("No SpaceSystem by name '" + ssname + "'"); } ss.addMetaCommand(c); commands.put(c.getQualifiedName(), c); commandAliases.add(c); XtceAliasSet aliases = c.getAliasSet(); if (aliases != null) { namespaces.addAll(aliases.getNamespaces()); } } finally { rwLock.writeLock().unlock(); } } /** * Adds a new spacesystem to the XTCE db. * * It throws an IllegalArgumentException in the following circumstances: * - if a SpaceSystem with this name already exists * - if system.getParent() does not return null * - if the parent SpaceSystem does not exist * - if the space system is not empty * * This method also sets the parent of the passed spacesystem to its parent object. * * Note that this method is used to create SpaceSystems on the fly. * The SpaceSystems are not saved anywhere and they will not be available when this object is created by the XtceDbFactory. * @param system * */ public void addSpaceSystem(SpaceSystem system) { rwLock.writeLock().lock(); try { if(system.getParent()!=null) throw new IllegalArgumentException("The parent of the space system has to be null (it will be set by this method"); if(!system.getParameters().isEmpty() || !system.getSequenceContainers().isEmpty()|| !system.getAlgorithms().isEmpty() || !system.getMetaCommands().isEmpty() || !system.getSubSystems().isEmpty()) throw new IllegalArgumentException("The space system must be empty (no parameters, containers, commands, algorithms, subsystems)"); String parentName = system.getSubsystemName(); SpaceSystem parent = spaceSystems.get(parentName); if(parent==null) throw new IllegalArgumentException("The parent subsystem '"+parentName+"' does not exist"); parent.addSpaceSystem(system); system.setParent(parent); spaceSystems.put(system.getQualifiedName(), system); spaceSystemAliases.add(system); XtceAliasSet aliases = system.getAliasSet(); if(aliases!=null) { aliases.getNamespaces().forEach(ns -> namespaces.add(ns)); } } finally { rwLock.writeLock().unlock(); } } public static boolean isSystemParameter(NamedObjectId id) { boolean result; if(!id.hasNamespace()) { result = id.getName().startsWith(XtceDb.YAMCS_SPACESYSTEM_NAME); } else { result = id.getNamespace().startsWith(XtceDb.YAMCS_SPACESYSTEM_NAME); } return result; } private static void createSpaceSystem(XtceDb xtceDb, String ssname) { String[] a = ssname.split("/"); String qn = ""; for(String name:a) { if(name.isEmpty()) continue; qn = qn+"/"+name; if(xtceDb.getSpaceSystem(qn) == null) { SpaceSystem ss = new SpaceSystem(name); ss.setQualifiedName(qn); xtceDb.addSpaceSystem(ss); } } } private void print(SpaceSystem ss, PrintStream out) { if( ss.getHeader() != null ) { out.println("=========SpaceSystem "+ss.getQualifiedName()+" version: "+ss.getHeader().getVersion()+" date: "+ss.getHeader().getDate()+"========="); } else { out.println("=========SpaceSystem "+ss.getQualifiedName()+" (no header information)========="); } Comparator<NameDescription> comparator=new Comparator<NameDescription>() { @Override public int compare(NameDescription o1, NameDescription o2) { return o1.getName().compareTo(o2.getName()); } }; SequenceContainer[] sca=ss.getSequenceContainers().toArray(new SequenceContainer[0]); Arrays.sort(sca, comparator); for (SequenceContainer sc : sca) { sc.print(out); } Algorithm[] aa=ss.getAlgorithms().toArray(new Algorithm[0]); Arrays.sort(aa, comparator); for (Algorithm a : aa) { a.print(out); } MetaCommand[] mca=ss.getMetaCommands().toArray(new MetaCommand[0]); Arrays.sort(mca, comparator); for (MetaCommand mc : mca) { mc.print(out); } //print the list of system variables if any (because those will not be part of the sequence containers) List<SystemParameter> systemVariables = new ArrayList<SystemParameter>(); for(Parameter p: ss.getParameters()) { if(p instanceof SystemParameter) { systemVariables.add((SystemParameter)p); } } if(!systemVariables.isEmpty()) { out.println("System Parameters: "); SystemParameter[] sva=systemVariables.toArray(new SystemParameter[0]); Arrays.sort(sva, comparator); for (SystemParameter sv : sva) { out.println("\t"+sv.getName()); } } SpaceSystem[] ssa=ss.getSubSystems().toArray(new SpaceSystem[0]); Arrays.sort(ssa, comparator); for(SpaceSystem ss1:ssa) { print(ss1, out); } } public void print(PrintStream out) { print(rootSystem, out); Set<Parameter> orphanedParameters = new HashSet<Parameter>(); orphanedParameters.addAll(parameters.values()); removeNonOrphaned(rootSystem, orphanedParameters); orphanedParameters.removeAll(parameter2ParameterEntryMap.keySet()); if(!orphanedParameters.isEmpty()) { out.println("================ Orphaned parameters (not referenced in any container or algorithm):"); for(Parameter p:orphanedParameters) { String namespaces = ""; if(p.getAliasSet()!=null) namespaces = ", aliases: "+ p.getAliasSet(); out.println(p.getQualifiedName()+", datasource: "+p.getDataSource()+ namespaces); } } } private void removeNonOrphaned(SpaceSystem ss, Set<Parameter> orphanedParameters) { for(Algorithm a:ss.getAlgorithms()) { for(InputParameter p:a.getInputSet()) { orphanedParameters.remove(p.getParameterInstance().getParameter()); } for(OutputParameter p:a.getOutputSet()) { orphanedParameters.remove(p.getParameter()); } } for(SpaceSystem ss1:ss.getSubSystems()) { removeNonOrphaned(ss1, orphanedParameters); } } }