package org.yamcs.parameter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.yamcs.ConfigurationException; import org.yamcs.DVParameterConsumer; import org.yamcs.InvalidIdentification; import org.yamcs.InvalidRequestIdentification; import org.yamcs.parameter.ParameterValue; import org.yamcs.Processor; import org.yamcs.alarms.AlarmServer; import org.yamcs.protobuf.Yamcs.NamedObjectId; import org.yamcs.utils.LoggingUtils; import org.yamcs.xtce.Parameter; import org.yamcs.xtceproc.AlarmChecker; import org.yamcs.xtceproc.XtceTmProcessor; /** * @author mache * Keeps track of which parameters are part of which subscriptions. * * There are two types of subscriptions: * - subscribe all * - subscribe to a set * Both types have an unique id associated but different methods work with them * */ public class ParameterRequestManagerImpl implements ParameterRequestManager { Logger log; //Maps the parameters to the request(subscription id) in which they have been asked private ConcurrentHashMap<Parameter, SubscriptionArray> param2RequestMap = new ConcurrentHashMap<Parameter, SubscriptionArray>(); //Maps the request (subscription id) to the consumer private Map<Integer, ParameterConsumer> request2ParameterConsumerMap = new ConcurrentHashMap<Integer,ParameterConsumer>(); //these are the consumers that may update the list of parameters // they are delivered with priority such that in onde update cycle the algorithms (or derived values) are also computed private Map<Integer,DVParameterConsumer> request2DVParameterConsumerMap = new HashMap<Integer,DVParameterConsumer>(); //contains subscribe all private SubscriptionArray subscribeAll = new SubscriptionArray(); private AlarmChecker alarmChecker; private Map<Class<?>,ParameterProvider> parameterProviders=new LinkedHashMap<Class<?>,ParameterProvider>(); private static AtomicInteger lastSubscriptionId= new AtomicInteger(); public final Processor yproc; //if all parameter shall be subscribed/processed private boolean cacheAll = false; AlarmServer alarmServer; SoftwareParameterManager spm; ParameterCache parameterCache; ParameterCacheConfig cacheConfig; /** * Creates a new ParameterRequestManager, configured to listen to the * specified XtceTmProcessor. */ public ParameterRequestManagerImpl(Processor yproc, XtceTmProcessor tmProcessor) throws ConfigurationException { this.yproc = yproc; log = LoggingUtils.getLogger(this.getClass(), yproc); cacheConfig = yproc.getPameterCacheConfig(); cacheAll = cacheConfig.cacheAll; tmProcessor.setParameterListener(this); addParameterProvider(tmProcessor); if(yproc.hasAlarmChecker()) { alarmChecker=new AlarmChecker(this, lastSubscriptionId.incrementAndGet()); } if(yproc.hasAlarmServer()) { alarmServer = new AlarmServer(yproc.getInstance(), "alarms_realtime"); alarmChecker.enableServer(alarmServer); } if(cacheConfig.enabled) { parameterCache = new ParameterCache(cacheConfig); } } public void addParameterProvider(ParameterProvider parameterProvider) { if(parameterProviders.containsKey(parameterProvider.getClass())) { log.warn("Ignoring duplicate parameter provider of type {}", parameterProvider.getClass()); } else { log.debug("Adding parameter provider: {}", parameterProvider.getClass()); parameterProvider.setParameterListener(this); parameterProviders.put(parameterProvider.getClass(), parameterProvider); if(parameterProvider instanceof SoftwareParameterManager) { spm = (SoftwareParameterManager) parameterProvider; } } } /** * This is called after all the parameter providers have been added but before the start. * We subscribe to all parameters if cacheAll is enabled * this way we give the opportunity to the ReplayService to find out what is required to retrieve from the ReplayServer */ public void init() { if(cacheAll) { for(ParameterProvider prov: parameterProviders.values()) { prov.startProvidingAll(); } } else if(alarmChecker!=null) { //at least get all that have alarms for(Parameter p:yproc.getXtceDb().getParameters()) { if(p.getParameterType()!=null && p.getParameterType().hasAlarm()) { ParameterProvider provider = getProvider(p); if(provider==null) { log.warn("No provider found for parameter {} which has alarms", p.getQualifiedName()); } else { log.debug("Asking provider {} to provide {} because it has alarms", provider, p.getQualifiedName()); provider.startProviding(p); } } } } } /** * subscribes to all parameters */ public int subscribeAll(ParameterConsumer consumer) { int id=lastSubscriptionId.incrementAndGet(); log.debug("new subscribeAll with subscriptionId {}", id); if(subscribeAll.isEmpty()) { for(ParameterProvider provider:parameterProviders.values()) { provider.startProvidingAll(); } } subscribeAll.add(id); request2ParameterConsumerMap.put(id, consumer); return id; } /** * removes the subscription to all parameters * * return true of the subscription has been removed or false if it was not there * @param subscriptionId * @return */ public boolean unsubscribeAll(int subscriptionId) { return subscribeAll.remove(subscriptionId); } public int addRequest(final List<Parameter> paraList, final ParameterConsumer tpc) throws InvalidIdentification { List<ParameterProvider> providers = getProviders(paraList); final int id=lastSubscriptionId.incrementAndGet(); log.debug("new request with subscriptionId {} with {} items", id, paraList.size()); for(int i=0;i<paraList.size();i++) { log.trace("adding to subscriptionID: {} item:{}, provider: {} ",id, paraList.get(i).getQualifiedName(), providers.get(i)); addItemToRequest(id, paraList.get(i), providers.get(i)); } request2ParameterConsumerMap.put(id, tpc); return id; } /** * Creates a request with one parameter * @param para * @param tpc * @return * @throws InvalidIdentification */ public int addRequest(final Parameter para, final ParameterConsumer tpc) throws InvalidIdentification { ParameterProvider provider = getProvider(para); final int id=lastSubscriptionId.incrementAndGet(); log.debug("new request with subscriptionId {} for parameter: {}, provider: {}", id, para.getQualifiedName(), provider); addItemToRequest(id, para, provider); request2ParameterConsumerMap.put(id, tpc); return id; } /** * Creates a new request with a list of parameters * @param paraList * @param dvtpc * @return * @throws InvalidIdentification */ public int addRequest(List<Parameter> paraList, DVParameterConsumer dvtpc) throws InvalidIdentification { List<ParameterProvider> providers=getProviders(paraList); int id=lastSubscriptionId.incrementAndGet(); log.debug("new request with subscriptionId {} for itemList={}", id, paraList); for(int i=0;i<paraList.size();i++) { log.trace("adding to subscriptionID:{} item:{}", id, paraList.get(i)); addItemToRequest(id, paraList.get(i), providers.get(i)); } request2DVParameterConsumerMap.put(id, dvtpc); return id; } /** * Create request with a given id. This is called when switching yprocessors, the id is coming from the other channel. * @param subscriptionId - subscription id * @param paraList * @param tpc * @throws InvalidIdentification */ public void addRequest(int subscriptionId, List<Parameter> paraList, ParameterConsumer tpc) throws InvalidIdentification { List<ParameterProvider> providers=getProviders(paraList); for(int i=0;i<paraList.size();i++) { log.trace("creating subscriptionID:{} with item:{}",subscriptionId, paraList.get(i)); addItemToRequest(subscriptionId, paraList.get(i), providers.get(i)); } request2ParameterConsumerMap.put(subscriptionId, tpc); } /** * Add items to an request id. * @param subscriptionId * @param para * @throws InvalidIdentification */ public void addItemsToRequest(final int subscriptionId, final Parameter para) throws InvalidIdentification, InvalidRequestIdentification { log.debug("adding to subscriptionID {}: items: {} ", subscriptionId, para); final ParameterConsumer consumer = request2ParameterConsumerMap.get(subscriptionId); if((consumer==null) && !request2DVParameterConsumerMap.containsKey(subscriptionId) && alarmChecker!=null && alarmChecker.getSubscriptionId()!=subscriptionId) { log.error(" addItemsToRequest called with an invalid subscriptionId={}\n current subscr:\n{}dv " + "subscr:\n {}", subscriptionId, request2ParameterConsumerMap, request2DVParameterConsumerMap); throw new InvalidRequestIdentification("no such subscriptionID",subscriptionId); } ParameterProvider provider = getProvider(para); addItemToRequest(subscriptionId, para, provider); } /** * Add items to a subscription. * @param subscriptionId * @param paraList - list of parameters that are added to the subscription * @throws InvalidIdentification * @throws InvalidRequestIdentification */ public void addItemsToRequest(final int subscriptionId, final List<Parameter> paraList) throws InvalidIdentification, InvalidRequestIdentification { log.debug("adding to subscriptionID {}: items: {} ", subscriptionId, paraList); final ParameterConsumer consumer = request2ParameterConsumerMap.get(subscriptionId); if((consumer==null) && !request2DVParameterConsumerMap.containsKey(subscriptionId)) { log.error(" addItemsToRequest called with an invalid subscriptionId={}\n current " + "subscr:\ndv subscr:\n{}", subscriptionId, request2ParameterConsumerMap, request2DVParameterConsumerMap); throw new InvalidRequestIdentification("no such subscriptionID",subscriptionId); } List<ParameterProvider> providers=getProviders(paraList); for(int i=0;i<paraList.size();i++) { addItemToRequest(subscriptionId, paraList.get(i), providers.get(i)); } } /** * Removes a parameter from a rquest. If it is not part of the request, the operation will have no effect. * @param subscriptionID * @param param * @throws InvalidIdentification */ public void removeItemsFromRequest(int subscriptionID, Parameter param) throws InvalidIdentification { ParameterProvider provider = getProvider(param); removeItemFromRequest(subscriptionID, param, provider); } /** * Removes a list of parameters from a request. * Any parameter specified that is not in the subscription will be ignored. * * @param subscriptionID * @param paraList */ public void removeItemsFromRequest(int subscriptionID, List<Parameter> paraList) { List<ParameterProvider> providers=getProviders(paraList); for(int i=0;i<paraList.size();i++) { removeItemFromRequest(subscriptionID, paraList.get(i), providers.get(i)); } } /** * Adds a new item to an existing request. There is no check if the item is already there, * so there can be duplicates (observed in the CGS CIS). * This call also works with a new id * @param id * @param para * @param provider * @throws InvalidIdentification */ private void addItemToRequest(int id, Parameter para, ParameterProvider provider) { if(!param2RequestMap.containsKey(para)) { //this parameter is not requested by any other request if(param2RequestMap.putIfAbsent(para, new SubscriptionArray())==null ) { if (!cacheAll) { provider.startProviding(para); } if (alarmChecker != null) { alarmChecker.parameterSubscribed(para); } } } SubscriptionArray al_req = param2RequestMap.get(para); al_req.add(id); } private void removeItemFromRequest(int subscriptionId, Parameter para, ParameterProvider provider) { if(param2RequestMap.containsKey(para)) { //is there really any request associated to this parameter? SubscriptionArray al_req=param2RequestMap.get(para); //remove the subscription from the list of this parameter if(al_req.remove(subscriptionId)) { /* Don't remove the al_req from the map and * don't ask provider to stop providing * because it is not thread safe (maybe another thread just asked to start providing after seeing that the list is empty if(al_req.isEmpty()) { //nobody wants this parameter anymore if(!cacheAll) provider.stopProviding(para); }*/ } else { log.warn("parameter removal requested for {}but not part of subscription {}", para , subscriptionId); } } else { log.warn("parameter removal requested for {} but not subscribed", para); } } /** * Removes all the items from this subscription and returns them into an * ArrayList. The result is usually used in the TelemetryImpl to move this * subscription to a different ParameterRequestManager */ public ArrayList<Parameter> removeRequest(int subscriptionId) { log.debug("removing request for subscriptionId {}", subscriptionId); //It's a bit annoying that we have to loop through all the parameters to find the ones that // are relevant for this request. We could keep track of an additional map. ArrayList<Parameter> result=new ArrayList<Parameter>(); //loop through all the parameter definitions // find all the subscriptions with the requested subscriptionId and add their corresponding // itemId to the list. Iterator<Map.Entry<Parameter, SubscriptionArray>> it = param2RequestMap.entrySet().iterator(); while(it.hasNext()) { Map.Entry<Parameter, SubscriptionArray> m = it.next(); Parameter param = m.getKey(); SubscriptionArray al_req = m.getValue(); if(al_req.remove(subscriptionId)) { result.add(param); } if(al_req.isEmpty()) { //nobody wants this parameter anymore /*if(!cacheAll) { commented out because not thread safe getProvider(param).stopProviding(param); }*/ } } request2ParameterConsumerMap.remove(subscriptionId); request2DVParameterConsumerMap.remove(subscriptionId); return result; } private ParameterProvider getProvider(Parameter param) { for(ParameterProvider provider:parameterProviders.values()) { if(provider.canProvide(param)) { return provider; } } throw new NoProviderException("No provider found for "+param); } private List<ParameterProvider> getProviders(List<Parameter> itemList) { List<ParameterProvider> providers=new ArrayList<ParameterProvider>(itemList.size()); for(int i=0;i<itemList.size();i++) { ParameterProvider prov = getProvider(itemList.get(i)); providers.add(i, prov); } return providers; } /** * returns a parameter based on fully qualified name * @param fqn * @return * @throws InvalidIdentification */ public Parameter getParameter(String fqn) throws InvalidIdentification { return getParameter(NamedObjectId.newBuilder().setName(fqn).build()); } /** * @param paraId * @return the corresponding parameter definition for a IntemIdentification * @throws InvalidIdentification in case no provider knows of this parameter. */ public Parameter getParameter(NamedObjectId paraId) throws InvalidIdentification { for(ParameterProvider provider:parameterProviders.values()) { if(provider.canProvide(paraId)) { return provider.getParameter(paraId); } } throw new InvalidIdentification(paraId); } @Override public void update(Collection<ParameterValue> params) { log.trace("ParamRequestManager.updateItems with {} parameters", params.size()); //maps subscription id to a list of (value,id) to be delivered for that subscription HashMap<Integer, ArrayList<ParameterValue>> delivery= new HashMap<Integer, ArrayList<ParameterValue>>(); //so first we add to the delivery the parameters just received updateDelivery(delivery, params); //then if the delivery updates some of the parameters required by the derived values // compute the derived values for(Map.Entry<Integer, DVParameterConsumer> entry: request2DVParameterConsumerMap.entrySet()) { Integer subscriptionId = entry.getKey(); if(delivery.containsKey(subscriptionId)) { updateDelivery(delivery, entry.getValue().updateParameters(subscriptionId, delivery.get(subscriptionId))); } } //and finally deliver the delivery :) for(Map.Entry<Integer, ArrayList<ParameterValue>> entry: delivery.entrySet()){ Integer subscriptionId=entry.getKey(); if(request2DVParameterConsumerMap.containsKey(subscriptionId)) { continue; } if(alarmChecker!=null && alarmChecker.getSubscriptionId()==subscriptionId){ continue; } ArrayList<ParameterValue> al=entry.getValue(); ParameterConsumer consumer = request2ParameterConsumerMap.get(subscriptionId); if(consumer==null) { log.warn("subscriptionId {} appears in the delivery list, but there is no consumer for it", subscriptionId); } else { consumer.updateItems(subscriptionId, al); } } } /** * adds the passed parameters to the delivery * @param delivery * @param params */ private void updateDelivery(HashMap<Integer, ArrayList<ParameterValue>> delivery, Collection<ParameterValue> params) { if(params==null) { return; } for(Iterator<ParameterValue> it=params.iterator();it.hasNext();) { ParameterValue pv = it.next(); Parameter pDef = pv.getParameter(); SubscriptionArray cowal = param2RequestMap.get(pDef); //now walk through the requests and add this item to their delivery list if(cowal==null) { continue; } for(int s:cowal.getArray()) { ArrayList<ParameterValue> al = delivery.get(s); if(al==null) { al = new ArrayList<ParameterValue>(); delivery.put(s, al); } al.add(pv); } } //update the subscribeAll subscriptions for(int id:subscribeAll.getArray()) { ArrayList<ParameterValue> al=delivery.get(id); if(al==null){ al = new ArrayList<ParameterValue>(); delivery.put(id, al); } for(ParameterValue pv: params) { al.add(pv); } } if(alarmChecker!=null) { //update the alarmChecker and check for alarms ArrayList<ParameterValue> pvlist = delivery.get(alarmChecker.getSubscriptionId()); if(pvlist!=null) { alarmChecker.updateParameters(pvlist); } try { alarmChecker.performAlarmChecking(params); } catch (Exception e) { log.error("Error when performing alarm checking ",e); } } if(parameterCache!=null) { parameterCache.update(params); } } /** * * @return the SoftwareParameterManager or null if not configured */ public SoftwareParameterManager getSoftwareParameterManager() { return spm; } @SuppressWarnings("unchecked") public <T extends ParameterProvider> T getParameterProvider(Class<T> type) { return (T) parameterProviders.get(type); } public AlarmChecker getAlarmChecker() { return alarmChecker; } @Override public String toString() { StringBuilder sb=new StringBuilder(); sb.append("Current Subscription list:\n"); for(Parameter param:param2RequestMap.keySet()) { sb.append(param); sb.append("requested by ["); SubscriptionArray al_req=param2RequestMap.get(param); for(int id: al_req.getArray()) { sb.append(id); } sb.append("]\n"); } return sb.toString(); } public AlarmServer getAlarmServer() { return alarmServer; } public boolean hasParameterCache() { return parameterCache!=null; } public List<ParameterValue> getValuesFromCache(List<Parameter> plist) { return parameterCache.getValues(plist); } /** * Get the last value from cache for a specific parameters * @param param * @return */ public ParameterValue getLastValueFromCache(Parameter param) { return parameterCache.getLastValue(param); } /** * Get all the values from cache for a specific parameters * * The parameter are returned in descending order (newest parameter is returned first) * @param param * @return */ public List<ParameterValue> getValuesFromCache(Parameter param) { return parameterCache.getAllValues(param); } public void start() { if(alarmServer!=null) { alarmServer.startAsync(); } } public ParameterCache getParameterCache() { return parameterCache; } }