package org.yamcs.cmdhistory; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.yamcs.ConfigurationException; import org.yamcs.Processor; import org.yamcs.commanding.InvalidCommandId; import org.yamcs.commanding.PreparedCommand; import org.yamcs.protobuf.Commanding.CommandHistoryAttribute; import org.yamcs.protobuf.Commanding.CommandHistoryEntry; import org.yamcs.protobuf.Commanding.CommandId; import org.yamcs.parameter.Value; import org.yamcs.utils.LoggingUtils; import org.yamcs.utils.ValueUtility; import org.yamcs.yarch.Stream; import com.google.common.util.concurrent.AbstractService; /** * Part of processors: handles filtered requests for command history. * * We handle two kind of subscriptions: * - subscription to specific commands * - subscription to all the commands but filtered on source and time. * * * It receives commands from the cmd_history stream * * @author nm * */ public class CommandHistoryRequestManager extends AbstractService { private ConcurrentHashMap<CommandId, CommandHistoryEntry> activeCommands = new ConcurrentHashMap<>(); private ConcurrentHashMap<CommandId, ConcurrentLinkedQueue<CommandHistoryConsumer>> cmdSubcriptions = new ConcurrentHashMap<>(); private ConcurrentHashMap<CommandHistoryFilter,CommandHistoryConsumer> historySubcriptions = new ConcurrentHashMap<>(); Stream realtimeCmdHistoryStream; static AtomicInteger subscriptionIdGenerator=new AtomicInteger(); final Logger log; //maps strings are requested in the getCommandHistory to strings as they appear in the commnad history AtomicInteger extendedId=new AtomicInteger(); final String instance; final Processor processor; public CommandHistoryRequestManager(Processor processor) throws ConfigurationException { this.processor = processor; this.instance = processor.getInstance(); log = LoggingUtils.getLogger(this.getClass(), processor); } /** * Add a consumer to the subscriber list for a command * @param cmdId * @param consumer * @return all the entries existing so far for the command * @throws InvalidCommandId */ public CommandHistoryEntry subscribeCommand(CommandId cmdId, CommandHistoryConsumer consumer) throws InvalidCommandId { CommandHistoryEntry che=activeCommands.get(cmdId); if(che!=null) { cmdSubcriptions.putIfAbsent(cmdId, new ConcurrentLinkedQueue<CommandHistoryConsumer>()); cmdSubcriptions.get(cmdId).add(consumer); return che; } log.warn("Received subscribe command for a command not in my active list: ({})", cmdId); throw new InvalidCommandId("command "+cmdId+" is not in the list of active commands",cmdId); } /** * removes a consumer from the subscribers for a command (if existing). * @param cmdId * @param consumer */ public void unsubscribeCommand(CommandId cmdId, CommandHistoryConsumer consumer) { CommandHistoryEntry che=activeCommands.get(cmdId); if(che!=null) { ConcurrentLinkedQueue<CommandHistoryConsumer> l=cmdSubcriptions.get(che); if(l!=null) l.remove(consumer); } } /** * Called by the CommandHistory consumers when they want to receive all updates corresponding * to a command. * @param commandsOrigin * @param commandsSince * @param consumer * @return */ public int subscribeCommandHistory(String commandsOrigin, long commandsSince, CommandHistoryConsumer consumer) { log.debug("commandsOrigin={}", commandsOrigin); CommandHistoryFilter filter = new CommandHistoryFilter(subscriptionIdGenerator.getAndIncrement(), commandsOrigin, commandsSince); historySubcriptions.put(filter,consumer); return filter.subscriptionId; } /** * Called by the CommandHistory consumers to remove the subscription * @param id */ public CommandHistoryFilter unsubscribeCommandHistory(int id) { for (CommandHistoryFilter f:historySubcriptions.keySet()) { if(f.subscriptionId==id) { historySubcriptions.remove(f); return f; } } return null; } /** * Called by the CommandHistoryImpl to move the subscription from another command history manager to this one * @param filter */ public void addSubscription(CommandHistoryFilter filter, CommandHistoryConsumer consumer) { historySubcriptions.put(filter, consumer); } /** * Called when a new command has to be added to the command history * (i.e. when a users sends a telecommand) */ public void addCommand(PreparedCommand pc) { log.debug("addCommand cmdId={}", pc); CommandHistoryEntry che = CommandHistoryEntry.newBuilder().setCommandId(pc.getCommandId()).build(); //deliver to clients for(Iterator<CommandHistoryFilter> it=historySubcriptions.keySet().iterator(); it.hasNext();) { CommandHistoryFilter filter=it.next(); if(filter.matches(che)){ historySubcriptions.get(filter).addedCommand(pc); } } activeCommands.put(pc.getCommandId(), che); } /** * send updates. * @param cmdId * @param key * @param value * @throws InvalidCommandId the command does not appear in the activeCommands hash * */ public void updateCommand(CommandId cmdId, String key, Value value) throws InvalidCommandId { log.debug("updateCommand cmdId={} key={} value={}", new Object[]{cmdId, key, value}); CommandHistoryEntry che=activeCommands.get(cmdId); if(che==null) { // If the commandId is valid, add the command in the active list, this case happens if an old command history is updated. che = CommandHistoryEntry.newBuilder().setCommandId(cmdId).build(); } CommandHistoryAttribute cha = CommandHistoryAttribute.newBuilder().setName(key).setValue(ValueUtility.toGbp(value)).build(); CommandHistoryEntry che1 = CommandHistoryEntry.newBuilder(che).addAttr(cha).build(); activeCommands.put(cmdId, che1); long changeDate = processor.getCurrentTime(); for(Iterator<CommandHistoryFilter> it=historySubcriptions.keySet().iterator();it.hasNext();) { CommandHistoryFilter filter = it.next(); if(filter.matches(che)){ historySubcriptions.get(filter).updatedCommand(cmdId, changeDate, key, value); } } ConcurrentLinkedQueue<CommandHistoryConsumer> consumers=cmdSubcriptions.get(cmdId); if(consumers!=null) { for(Iterator<CommandHistoryConsumer> it=consumers.iterator();it.hasNext();) { it.next().updatedCommand(cmdId, changeDate, key, value); } } } @Override protected void doStart() { notifyStarted(); } @Override protected void doStop() { notifyStopped(); } public String getInstance() { return instance; } }