package org.yamcs.parameterarchive; import java.io.PrintStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.yamcs.protobuf.Yamcs.Value; import org.yamcs.protobuf.Yamcs.Value.Type; /** * Stores a map between * (parameter_fqn, type) and parameter_id * type is a 32 bit assigned corresponding (engType, rawType) * * engType and rawType are one of the types from protobuf Value.Type - we use the numbers assuming that no more than 2^15 will ever exist. * * * Backed by RocksDB * * @author nm * */ public class ParameterIdDb { final RocksDB db; final ColumnFamilyHandle p2pid_cfh; final static int TIMESTAMP_PARA_ID=0; //parameter fqn -> parameter type -> parameter id Map<String, Map<Integer, Integer>> p2pidCache = new HashMap<>(); int highestParaId = TIMESTAMP_PARA_ID; ParameterIdDb(RocksDB db, ColumnFamilyHandle p2pid_cfh) { this.db = db; this.p2pid_cfh = p2pid_cfh; readDb(); } /** * Get the mapping from parameter_name, type to parameter_id * * It creates it if it does not exist * * * @param paramFqn * @param engType * @param rawType * * @return a parameter id for the given parameter name and type * @throws ParameterArchiveException if there was an error creating and storing a new parameter_id */ public synchronized int createAndGet(String paramFqn, Value.Type engType, Value.Type rawType) throws ParameterArchiveException { int type = numericType(engType, rawType); Map<Integer, Integer> m = p2pidCache.get(paramFqn); if(m==null) { m = new HashMap<>(); p2pidCache.put(paramFqn, m); } Integer pid = m.get(type); if(pid==null) { pid = ++highestParaId; m.put(type, pid); store(paramFqn); } return pid; } /** * get a parameter id for a parameter that only has engineering value * @param paramFqn * @param engType * @return a parameter id for the given parameter name and type */ public int createAndGet(String paramFqn, Type engType) { return createAndGet(paramFqn, engType, null); } //compose a numeric type from engType and rawType (we assume that no more than 2^15 types will ever exist) private int numericType(Value.Type engType, Value.Type rawType) { int et = (engType==null)? 0xFFFF:engType.getNumber(); int rt = (rawType==null)? 0xFFFF:rawType.getNumber(); return et<<16|rt; } private void store(String paramFqn) throws ParameterArchiveException { Map<Integer, Integer> m = p2pidCache.get(paramFqn); byte[] key = paramFqn.getBytes(StandardCharsets.UTF_8); ByteBuffer bb = ByteBuffer.allocate(8*m.size()); for(Map.Entry<Integer, Integer> me:m.entrySet()) { bb.putInt(me.getKey()); bb.putInt(me.getValue()); } try { db.put(p2pid_cfh, key, bb.array()); } catch (RocksDBException e) { throw new ParameterArchiveException("Cannot store key for new parameter id", e); } } private void readDb() { try(RocksIterator it = db.newIterator(p2pid_cfh)) { it.seekToFirst(); while(it.isValid()) { byte[] pfqn = it.key(); byte[] pIdTypeList = it.value(); String paraName = new String(pfqn, StandardCharsets.UTF_8); Map<Integer, Integer> m = new HashMap<Integer, Integer>(); p2pidCache.put(paraName, m); ByteBuffer bb = ByteBuffer.wrap(pIdTypeList); while(bb.hasRemaining()) { int type = bb.getInt(); int pid = bb.getInt(); m.put(type, pid); if(pid > highestParaId) { highestParaId = pid; } } it.next(); } } } static Value.Type getType(int x) { if(x==0xFFFF) { return null; } else return Value.Type.valueOf(x); } public void print(PrintStream out) { for(String pname: p2pidCache.keySet()) { out.println(pname+": "); Map<Integer, Integer> m = p2pidCache.get(pname); for(Map.Entry<Integer, Integer> e: m.entrySet()) { int parameterId = e.getValue(); int et = e.getKey()>>16; int rt = e.getKey()&0xFFFF; out.println("\t("+getType(et)+", "+getType(rt)+") -> "+parameterId); } } } /* * return the number of unique parameters */ public int getSize() { return p2pidCache.size(); } /** * Get all parameters ids for a given qualified name * * return null if no parameter id exists for that fqn. * * * @param fqn - fully qualified name of the parameter for which the ids are returned * @return all parameters ids for a given qualified name or null if no parameter id exists for that fqn */ public synchronized ParameterId[] get(String fqn) { Map<Integer, Integer> m = p2pidCache.get(fqn); if(m==null) { return null; } ParameterId[] r = new ParameterId[m.size()]; int i = 0; for(Entry<Integer, Integer> e: m.entrySet()) { r[i++]=new ParameterId(e.getValue(), e.getKey()); } return r; } /** * returns the parameter FQN for the given parameterId - relatively expensive operation * @param parameterId * * @return parameterFQN or null if there is no parameter with the given id */ public String getParameterbyId(int parameterId) { for(Map.Entry<String, Map<Integer, Integer>> e: p2pidCache.entrySet()) { Map<Integer, Integer> m = e.getValue(); if(m.containsValue(parameterId)) { return e.getKey(); } } return null; } public static class ParameterId { public final int pid; public final Type engType; public final Type rawType; public ParameterId(int pid, int numericType) { this.pid = pid; int et = numericType>>16; int rt = numericType&0xFFFF; this.engType = getType(et); this.rawType = getType(rt); } @Override public String toString() { return "ParameterId [pid=" + pid + ", engType=" + engType + ", rawType=" + rawType + "]"; } } }