package org.yamcs;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.slf4j.Logger;
import org.yamcs.cmdhistory.CommandHistoryProvider;
import org.yamcs.cmdhistory.CommandHistoryPublisher;
import org.yamcs.cmdhistory.CommandHistoryRequestManager;
import org.yamcs.cmdhistory.StreamCommandHistoryProvider;
import org.yamcs.cmdhistory.YarchCommandHistoryAdapter;
import org.yamcs.commanding.CommandQueueManager;
import org.yamcs.commanding.CommandReleaser;
import org.yamcs.commanding.CommandingManager;
import org.yamcs.container.ContainerRequestManager;
import org.yamcs.parameter.ParameterCache;
import org.yamcs.parameter.ParameterCacheConfig;
import org.yamcs.parameter.ParameterProvider;
import org.yamcs.parameter.ParameterRequestManagerImpl;
import org.yamcs.protobuf.Yamcs.ReplayRequest;
import org.yamcs.protobuf.Yamcs.ReplaySpeed;
import org.yamcs.protobuf.Yamcs.ReplayStatus.ReplayState;
import org.yamcs.protobuf.YamcsManagement.ServiceState;
import org.yamcs.tctm.ArchiveTmPacketProvider;
import org.yamcs.tctm.TcTmService;
import org.yamcs.time.TimeService;
import org.yamcs.utils.LoggingUtils;
import org.yamcs.xtce.XtceDb;
import org.yamcs.xtceproc.ProcessorData;
import org.yamcs.xtceproc.XtceDbFactory;
import org.yamcs.xtceproc.XtceTmProcessor;
import com.google.common.util.concurrent.AbstractService;
import com.google.common.util.concurrent.Service;
/**
*
* This class helps keeping track of the different objects used in a Yamcs Processor - i.e. all the
* objects required to have a TM/TC processing chain (either realtime or playback).
*
*
*/
public class Processor extends AbstractService {
private static final String CONFIG_KEY_TM_PROCESSOR ="tmProcessor";
private static final String CONFIG_KEY_PARAMETER_CACHE ="parameterCache";
private static final String CONFIG_KEY_ALARM ="alarm";
private static Map<String,Processor>instances=Collections.synchronizedMap(new LinkedHashMap<>());
private ParameterRequestManagerImpl parameterRequestManager;
private ContainerRequestManager containerRequestManager;
private CommandHistoryPublisher commandHistoryPublisher;
private CommandHistoryRequestManager commandHistoryRequestManager;
private CommandingManager commandingManager;
private CommandHistoryProvider commandHistoryProvider;
private TmPacketProvider tmPacketProvider;
private CommandReleaser commandReleaser;
private List<ParameterProvider> parameterProviders = new ArrayList<ParameterProvider>();
private XtceDb xtcedb;
private String name;
private String type;
private final String yamcsInstance;
private boolean checkAlarms = true;
private boolean alarmServerEnabled = false;
private String creator="system";
private boolean persistent=false;
ParameterCacheConfig parameterCacheConfig = new ParameterCacheConfig(false, false, 0, 0);
final Logger log;
static Set<ProcessorListener> listeners=new CopyOnWriteArraySet<>(); //send notifications for added and removed processors to this
private boolean quitting;
//a synchronous processor waits for all the clients to deliver tm packets and parameters
private boolean synchronous=false;
XtceTmProcessor tmProcessor;
//unless very good performance reasons, we should try to serialize all the processing in this thread
private final ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private final ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1);
TimeService timeService;
ProcessorData processorData = new ProcessorData();
@GuardedBy("this")
HashSet<ProcessorClient> connectedClients= new HashSet<ProcessorClient>();
public Processor(String yamcsInstance, String name, String type, String creator) throws ProcessorException {
if((name==null) || "".equals(name)) {
throw new ProcessorException("The processor name must not be empty");
}
this.yamcsInstance = yamcsInstance;
this.name = name;
this.creator = creator;
this.type = type;
log = LoggingUtils.getLogger(Processor.class, this);
log.info("Creating new processor '{}' of type '{}'", name, type);
}
@SuppressWarnings("unchecked")
void init(TcTmService tctms, Map<String, Object> config) throws ProcessorException, ConfigurationException {
xtcedb = XtceDbFactory.getInstance(yamcsInstance);
timeService = YamcsServer.getTimeService(yamcsInstance);
Map<String, Object> tmProcessorConfig = null;
synchronized(instances) {
if(instances.containsKey(key(yamcsInstance,name))) {
throw new ProcessorException("A processor named '"+name+"' already exists in instance "+yamcsInstance);
}
if(config!=null) {
for(String c: config.keySet()) {
if(CONFIG_KEY_ALARM.equals(c)) {
Object o = config.get(c);
if(!(o instanceof Map)) {
throw new ConfigurationException(CONFIG_KEY_ALARM+" configuration should be a map");
}
configureAlarms((Map<String, Object>) o);
} else if(CONFIG_KEY_PARAMETER_CACHE.equals(c)) {
Object o = config.get(c);
if(!(o instanceof Map)) {
throw new ConfigurationException(CONFIG_KEY_PARAMETER_CACHE + " configuration should be a map");
}
configureParameterCache((Map<String, Object>) o);
} else if(CONFIG_KEY_TM_PROCESSOR.equals(c)) {
Object o = config.get(c);
if(!(o instanceof Map)) {
throw new ConfigurationException(CONFIG_KEY_TM_PROCESSOR+ " configuration should be a map");
}
tmProcessorConfig = (Map<String, Object>) o;
} else {
log.warn("Ignoring unknown config key '{}'", c);
}
}
}
this.tmPacketProvider=tctms.getTmPacketProvider();
this.commandReleaser=tctms.getCommandReleaser();
List<ParameterProvider> providers = tctms.getParameterProviders();
if(providers!=null) {
this.parameterProviders.addAll(providers);
}
synchronous = tctms.isSynchronous();
// Shared between prm and crm
tmProcessor = new XtceTmProcessor(this, tmProcessorConfig);
if(tmPacketProvider!=null) {
tmPacketProvider.init(this, tmProcessor);
}
containerRequestManager = new ContainerRequestManager(this, tmProcessor);
parameterRequestManager = new ParameterRequestManagerImpl(this, tmProcessor);
for(ParameterProvider pprov: parameterProviders) {
pprov.init(this);
parameterRequestManager.addParameterProvider(pprov);
}
if((tmPacketProvider!=null) && (tmPacketProvider instanceof ParameterProvider) ) {
parameterRequestManager.addParameterProvider((ParameterProvider)tmPacketProvider);
}
parameterRequestManager.init();
if(commandReleaser!=null) {
try {
this.commandHistoryPublisher=new YarchCommandHistoryAdapter(yamcsInstance);
} catch (Exception e) {
throw new ConfigurationException("Cannot create command history" , e);
}
commandingManager=new CommandingManager(this);
commandReleaser.setCommandHistory(commandHistoryPublisher);
commandHistoryRequestManager = new CommandHistoryRequestManager(this);
commandHistoryProvider = new StreamCommandHistoryProvider();
commandHistoryProvider.setCommandHistoryRequestManager(commandHistoryRequestManager);
} else {
commandingManager=null;
if((tmPacketProvider!=null) && (tmPacketProvider instanceof CommandHistoryProvider) ) {
commandHistoryProvider = (CommandHistoryProvider) tmPacketProvider;
commandHistoryRequestManager = new CommandHistoryRequestManager(this);
commandHistoryProvider.setCommandHistoryRequestManager(commandHistoryRequestManager);
}
}
instances.put(key(yamcsInstance,name),this);
listeners.forEach(l -> l.processorAdded(this));
}
}
public ExecutorService getExecutor() {
return executor;
}
private void configureAlarms(Map<String, Object> alarmConfig) {
Object v = alarmConfig.get("check");
if(v!=null) {
if(!(v instanceof Boolean)) {
throw new ConfigurationException("Unknown value '"+v+"' for alarmConfig -> check. Boolean expected.");
}
checkAlarms = (Boolean)v;
}
v = alarmConfig.get("server");
if(v!=null) {
if(!(v instanceof String)) {
throw new ConfigurationException("Unknown value '"+v+"' for alarmConfig -> server. String expected.");
}
alarmServerEnabled = "enabled".equalsIgnoreCase((String)v);
if(alarmServerEnabled) {
checkAlarms=true;
}
}
}
private void configureParameterCache(Map<String, Object> cacheConfig) {
boolean enabled = false;
boolean cacheAll = false;
Object v = cacheConfig.get("enabled");
if(v!=null) {
if(!(v instanceof Boolean)) {
throw new ConfigurationException("Unknown value '"+v+"' for parameterCache -> enabled. Boolean expected.");
}
enabled = (Boolean)v;
}
if(!enabled) { //this is the default but print a warning if there are some things configured
Set<String> keySet = cacheConfig.keySet();
keySet.remove("enabled");
if(!keySet.isEmpty()) {
log.warn("Parmeter cache is disabled, the following keys are ignored: {}, use enable: true to enable the parameter cache", keySet);
}
return;
}
v = cacheConfig.get("cacheAll");
if(v!=null) {
if(!(v instanceof Boolean)) {
throw new ConfigurationException("Unknown value '"+v+"' for parameterCache -> cacheAll. Boolean expected.");
}
cacheAll = (Boolean)v;
if(cacheAll) {
enabled=true;
}
}
long duration = 1000L * YConfiguration.getInt(cacheConfig, "duration", 600);
int maxNumEntries = YConfiguration.getInt(cacheConfig, "maxNumEntries", 4096);
parameterCacheConfig = new ParameterCacheConfig(enabled, cacheAll, duration, maxNumEntries);
}
private static String key(String instance, String name) {
return instance+"."+name;
}
public CommandHistoryPublisher getCommandHistoryPublisher() {
return commandHistoryPublisher;
}
public ParameterRequestManagerImpl getParameterRequestManager() {
return parameterRequestManager;
}
public ContainerRequestManager getContainerRequestManager() {
return containerRequestManager;
}
public XtceTmProcessor getTmProcessor() {
return tmProcessor;
}
/**
* starts processing by invoking the start method for all the associated components
*
*/
@Override
public void doStart() {
try {
if(tmPacketProvider!=null) {
tmPacketProvider.startAsync();
}
if(tmProcessor!=null) {
tmProcessor.startAsync();
}
if(commandReleaser!=null) {
commandReleaser.startAsync();
commandReleaser.awaitRunning();
commandingManager.startAsync();
commandingManager.awaitRunning();
CommandQueueManager cqm = commandingManager.getCommandQueueManager();
cqm.startAsync();
cqm.awaitRunning();
}
if(commandHistoryRequestManager!=null) {
commandHistoryRequestManager.startAsync();
startIfNecessary(commandHistoryProvider);
commandHistoryRequestManager.awaitRunning();
commandHistoryProvider.awaitRunning();
}
for(ParameterProvider pprov: parameterProviders) {
pprov.startAsync();
}
parameterRequestManager.start();
if(tmPacketProvider!=null) {
tmPacketProvider.awaitRunning();
}
if(tmProcessor!=null) {
tmProcessor.awaitRunning();
}
notifyStarted();
} catch (Exception e) {
notifyFailed(e);
}
propagateProcessorStateChange();
}
private void startIfNecessary(Service service) {
if(service.state()==State.NEW) {
service.startAsync();
}
}
public void pause() {
((ArchiveTmPacketProvider)tmPacketProvider).pause();
propagateProcessorStateChange();
}
public void resume() {
((ArchiveTmPacketProvider)tmPacketProvider).resume();
propagateProcessorStateChange();
}
private void propagateProcessorStateChange() {
listeners.forEach(l -> l.processorStateChanged(this));
}
public void seek(long instant) {
getTmProcessor().resetStatistics();
((ArchiveTmPacketProvider)tmPacketProvider).seek(instant);
propagateProcessorStateChange();
}
public void changeSpeed(ReplaySpeed speed) {
((ArchiveTmPacketProvider)tmPacketProvider).changeSpeed(speed);
propagateProcessorStateChange();
}
/**
* @return the tcUplinker
*/
public CommandReleaser getCommandReleaser() {
return commandReleaser;
}
/**
* @return the tmPacketProvider
*/
public TmPacketProvider getTmPacketProvider() {
return tmPacketProvider;
}
public String getName() {
return name;
}
/**
* @return the type
*/
public String getType() {
return type;
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
this.creator = creator;
}
public int getConnectedClients() {
return connectedClients.size();
}
public static Processor getInstance(String yamcsInstance, String name) {
return instances.get(key(yamcsInstance, name));
}
/**
* Returns the first register processor for the given instance or null if there is no processor registered.
*
* @param yamcsInstance - instance name for which the processor has to be returned.
* @return the first registered processor for the given instance
*/
public static Processor getFirstProcessor(String yamcsInstance) {
for(String k: instances.keySet()) {
if(k.startsWith(yamcsInstance+".")) {
return instances.get(k);
}
}
return null;
}
/**
* Increase with one the number of connected clients to the named processor and return the processor.
*
* @param yamcsInstance
* @param name
* @param s
* @return the processor with the given name
* @throws ProcessorException
*/
public static Processor connect(String yamcsInstance, String name, ProcessorClient s) throws ProcessorException {
Processor ds = instances.get(key(yamcsInstance, name));
if(ds==null) {
throw new ProcessorException("There is no processor named '"+name+"'");
}
ds.connect(s);
return ds;
}
/**
* Increase with one the number of connected clients
*/
public synchronized void connect(ProcessorClient s) throws ProcessorException {
log.debug("Session {} has one more user: {}", name, s);
if(quitting) {
throw new ProcessorException("This processor has been closed");
}
connectedClients.add(s);
}
/**
* Disconnects a client from this processor. If the processor has no more clients, quit.
*
*/
public void disconnect(ProcessorClient s) {
if(quitting) {
return;
}
boolean hasToQuit=false;
synchronized(this) {
connectedClients.remove(s);
log.info("Processor {} has one less user: connectedUsers: {}", name, connectedClients.size());
if((connectedClients.isEmpty())&&(!persistent)) {
hasToQuit=true;
}
}
if(hasToQuit) {
stopAsync();
}
}
public static Collection<Processor> getProcessors() {
return instances.values();
}
public static Collection<Processor> getProcessors(String instance) {
List<Processor> processors = new ArrayList<>();
for (Processor processor : instances.values()) {
if (instance.equals(processor.getInstance())) {
processors.add(processor);
}
}
return instances.values();
}
/**
* Closes the processor by stoping the tm/pp and tc
* It can be that there are still clients connected, but they will not get any data and new clients can not connect to
* these processors anymore. Once it is closed, you can create a processor with the same name which will make it maybe a bit
* confusing :(
*
*/
@Override
public void doStop() {
if(quitting) {
return;
}
log.info("Processor {} quitting", name);
quitting = true;
instances.remove(key(yamcsInstance,name));
for(ParameterProvider p:parameterProviders) {
p.stopAsync();
}
if(commandReleaser!=null) {
commandReleaser.stopAsync();
}
if(tmProcessor!=null) {
tmProcessor.stopAsync();
}
if(tmPacketProvider!=null) {
tmPacketProvider.stopAsync();
}
log.info("Processor {} is out of business", name);
listeners.forEach(l -> l.processorClosed(this));
synchronized(this) {
for(ProcessorClient s:connectedClients) {
s.processorQuit();
}
}
if(getState() == ServiceState.RUNNING || getState() == ServiceState.STOPPING) {
notifyStopped();
}
}
public static void addProcessorListener(ProcessorListener processorListener) {
listeners.add(processorListener);
}
public static void removeProcessorListener(ProcessorListener processorListener) {
listeners.remove(processorListener);
}
public boolean isPersistent() {
return persistent;
}
public void setPersistent(boolean systemSession) {
this.persistent = systemSession;
}
public boolean isSynchronous() {
return synchronous;
}
public boolean hasCommanding() {
return commandingManager!=null;
}
public void setSynchronous(boolean synchronous) {
this.synchronous = synchronous;
}
public boolean isReplay() {
if(tmPacketProvider==null){
return false;
}
return tmPacketProvider.isArchiveReplay();
}
/**
* valid only if isArchiveReplay returns true
* @return
*/
public ReplayRequest getReplayRequest() {
return ((ArchiveTmPacketProvider)tmPacketProvider).getReplayRequest();
}
/**
* valid only if isArchiveReplay returns true
* @return
*/
public ReplayState getReplayState() {
return ((ArchiveTmPacketProvider)tmPacketProvider).getReplayState();
}
public ServiceState getState() {
return ServiceState.valueOf(state().name());
}
public CommandingManager getCommandingManager() {
return commandingManager;
}
@Override
public String toString() {
return "name: "+name+" type: "+type+" connectedClients:"+connectedClients.size();
}
/**
*
* @return the yamcs instance this processor is part of
*/
public String getInstance() {
return yamcsInstance;
}
public XtceDb getXtceDb() {
return xtcedb;
}
public CommandHistoryRequestManager getCommandHistoryManager() {
return commandHistoryRequestManager;
}
public boolean hasAlarmChecker() {
return checkAlarms;
}
public boolean hasAlarmServer() {
return alarmServerEnabled;
}
public ScheduledThreadPoolExecutor getTimer() {
return timer;
}
/**
* Returns the processor time
*
* for realtime processors it is the mission time or simulation time
* for replay processors it is the replay time
* @return
*/
public long getCurrentTime() {
if(isReplay()) {
return ((ArchiveTmPacketProvider)tmPacketProvider).getReplayTime();
} else {
return timeService.getMissionTime();
}
}
public void quit() {
stopAsync();
awaitTerminated();
}
public void start() {
startAsync();
awaitRunning();
}
public void notifyStateChange() {
propagateProcessorStateChange();
}
/**
* returns a list of all processors names
*
* @return all processors names as a list of instance.processorName
*/
public static List<String> getAllProcessors() {
List<String> l = new ArrayList<String>(instances.size());
l.addAll(instances.keySet());
return l;
}
public ParameterCacheConfig getPameterCacheConfig() {
return parameterCacheConfig;
}
public ParameterCache getParameterCache() {
return parameterRequestManager.getParameterCache();
}
/**
* Returns the processor data used to store processor specific calibration, alarms
*
* @return processor specific data
*/
public ProcessorData getProcessorData() {
return processorData;
}
}