/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package alma.acs.nsstatistics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.StringUtils;
import org.omg.CORBA.OBJECT_NOT_EXIST;
import org.omg.CORBA.ORB;
import org.omg.CosNaming.Binding;
import org.omg.CosNaming.BindingIteratorHolder;
import org.omg.CosNaming.BindingListHolder;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContext;
import org.omg.CosNaming.NamingContextHelper;
import org.omg.CosNaming.NamingContextPackage.CannotProceed;
import org.omg.CosNaming.NamingContextPackage.NotFound;
import org.omg.CosNotifyChannelAdmin.AdminNotFound;
import org.omg.CosNotifyChannelAdmin.ChannelNotFound;
import org.omg.CosNotifyChannelAdmin.ConsumerAdmin;
import org.omg.CosNotifyChannelAdmin.EventChannel;
import org.omg.CosNotifyChannelAdmin.EventChannelFactory;
import org.omg.CosNotifyChannelAdmin.EventChannelFactoryHelper;
import org.omg.CosNotifyChannelAdmin.EventChannelHelper;
import org.omg.CosNotifyChannelAdmin.ProxyNotFound;
import org.omg.CosNotifyChannelAdmin.ProxySupplier;
import org.omg.DynamicAny.DynAnyFactory;
import org.omg.DynamicAny.DynAnyFactoryHelper;
import gov.sandia.CosNotification.NotificationServiceMonitorControl;
import gov.sandia.CosNotification.NotificationServiceMonitorControlHelper;
import alma.ACSErrTypeCommon.wrappers.AcsJIllegalArgumentEx;
import alma.ACSErrTypeCommon.wrappers.AcsJUnexpectedExceptionEx;
import alma.acs.component.client.AdvancedComponentClient;
import alma.acs.concurrent.ThreadLoopRunner;
import alma.acs.concurrent.ThreadLoopRunner.CancelableRunnable;
import alma.acs.concurrent.ThreadLoopRunner.ScheduleDelayMode;
import alma.acs.container.ContainerServices;
import alma.acs.exceptions.AcsJException;
import alma.acs.logging.AcsLogLevel;
import alma.acs.logging.ClientLogManager;
import alma.acs.nc.ArchiveConsumer;
import alma.acs.nc.Helper;
import alma.acs.nc.NCSubscriber;
import alma.acs.util.AcsLocations;
import alma.acscommon.ACS_NC_DOMAIN_ARCHIVING;
import alma.acscommon.ACS_NC_DOMAIN_LOGGING;
import alma.acscommon.NOTIFICATION_FACTORY_NAME;
/**
* @author jschwarz, hsommer
*
* $Id: EventModel.java,v 1.39 2013/02/22 15:36:44 hsommer Exp $
*/
public class EventModel {
private final static String nsStatsId = "nsStatistics";
/**
* Singleton instance, used by GUI layer classes to access the model.
*/
private static EventModel modelInstance;
/**
* Key = service ID (e.g. "AlarmNotifyEventChannelFactory") <br>
* Value = NotifyServiceData object
* <p>
* This is the root of our data model. From here we can go to NCs and their attributes.
*/
private final Map<String, NotifyServiceData> notifyServices;
private AdvancedComponentClient acc;
private final ORB orb;
private final Logger m_logger;
private final ContainerServices cs;
private final DynAnyFactory dynAnyFactory;
private NamingContext nctx;
private final ArrayBlockingQueue<EventData> equeue = new ArrayBlockingQueue<EventData>(50000);
private final ArrayBlockingQueue<ArchiveEventData> archQueue = new ArrayBlockingQueue<ArchiveEventData>(100000);
/**
* Once a notify service was found to be unreachable, we skip it when updating the lists of services, channels etc,
* but we still have to check regularly if it has come alive again.
* It is done in a separate thread, using this ThreadLoopRunner.
*/
private final ThreadLoopRunner unreachableServiceChecker;
/**
* key = name of NotifyService or NC; value=int[] {consumerCount, supplierCount}
*/
private final HashMap<String, int[]> lastConsumerAndSupplierCount;
/**
* Consumers used by the eventGUI to subscribe to events on various NCs.
* <p>
* Todo: we could keep the consumer reference in the matching ChannelData object
* and eliminate this map.
*/
private final HashMap<String, AdminConsumer> consumerMap;
/**
* Subscribes to monitoring/archiving events.
*/
private ArchiveConsumer archiveConsumer;
/**
* TODO: Remove singleton pattern and either register domain objects in a high (shared) node of IEclipseContexts,
* to be created by the Eclipse DI container,
* or turn the entire domain layer into an OSGI service (which is again a singleton...).
* See also http://www.eclipse.org/forums/index.php/t/333467/,
* http://blog.maxant.co.uk/pebble/2011/08/04/1312486560000.html
* <p>
* TODO: move the detection of NotifyService out of the ctor, to make the application start faster.
* This would require a first-time service-detection-independently-of-NCs mechanism.
* <p>
* We declare <code>Throwable</code> so that this constructor can catch, print and re-throw even the nastiest errors,
* which in case of VerifyError etc are otherwise not well shown by the Eclipse container.
*/
private EventModel() throws Throwable {
try {
notifyServices = new HashMap<String, NotifyServiceData>();
lastConsumerAndSupplierCount = new HashMap<String, int[]>();
consumerMap = new HashMap<String, AdminConsumer>();
m_logger = ClientLogManager.getAcsLogManager().getLoggerForApplication(nsStatsId, true);
//ClientLogManager.getAcsLogManager().suppressRemoteLogging();
String managerLoc = AcsLocations.figureOutManagerLocation();
acc = new AdvancedComponentClient(m_logger, managerLoc, nsStatsId) {
@Override
protected void initAlarmSystem() {
m_logger.fine("The eventGUI suppresses initialization of the alarm system libraries, to cut the unnecessary dependency on CERN AS jar files.");
}
@Override
protected void tearDownAlarmSystem() {
// nothing. Overloaded to avoid "java.lang.IllegalStateException: Trying close with null ContainerServicesBase"
}
};
cs = acc.getContainerServices();
orb = acc.getAcsCorba().getORB();
dynAnyFactory = DynAnyFactoryHelper.narrow(orb.resolve_initial_references("DynAnyFactory"));
nctx = NamingContextHelper.narrow(
acc.getAcsManagerProxy().get_service("NameService", false)
);
discoverNotifyServicesAndChannels();
// Set up periodic asynchronous status checks for services that become unreachable.
// TODO: Or should we check all services here, independently of the eventGUI refreshes selected by the user?
CancelableRunnable unreachableServiceCheckerRunnable = new CancelableRunnable() {
@Override
public void run() {
for (NotifyServiceData service : new ArrayList<NotifyServiceData>(notifyServices.values())) {
if (shouldTerminate) {
break;
}
if (!service.isReachable()) {
updateReachability(service);
}
}
}
};
unreachableServiceChecker = new ThreadLoopRunner(unreachableServiceCheckerRunnable, 5, TimeUnit.SECONDS, cs.getThreadFactory(), m_logger, "UnreachableServiceChecker");
unreachableServiceChecker.setDelayMode(ScheduleDelayMode.FIXED_DELAY);
unreachableServiceChecker.runLoop();
// todo: make this on-demand
getArchiveConsumer();
} catch (Throwable thr) {
thr.printStackTrace();
throw thr;
}
m_logger.info("*** EventModel() ctor finished ***");
}
public synchronized static EventModel getInstance() throws Throwable {
if (modelInstance == null)
modelInstance = new EventModel();
return modelInstance;
}
/**
* @TODO: Logger should be created separately and get injected
*/
public Logger getLogger() {
return m_logger;
}
/**
* Gets the event queue, for read and write access.
*/
public BlockingQueue<EventData> getEventQueue() {
return equeue;
}
public BlockingQueue<ArchiveEventData> getArchQueue() {
return archQueue;
}
public boolean reconnectNamingService() {
boolean ret = true;
try {
nctx = NamingContextHelper.narrow(
acc.getAcsManagerProxy().get_service("NameService", false)
);
m_logger.warning("Naming service has been recovered therefore all registered Notify Services will be removed to be loaded again");
notifyServices.clear();
} catch(Throwable thr) {
ret = false;
}
return ret;
}
/**
* Discovers services and bindings, retrieving only once the list of all
* bindings from the naming service.
*/
private synchronized boolean discoverNotifyServicesAndChannels() {
boolean ret = true;
BindingListHolder bl = new BindingListHolder();
BindingIteratorHolder bi = new BindingIteratorHolder();
try {
// Get names of all objects bound in the naming service
nctx.list(-1, bl, bi);
// Extract the useful binding information Id and Kind
Map<String, String> bindingMap = new HashMap<String, String>();
for (Binding binding : bl.value) {
bindingMap.put(binding.binding_name[0].id, binding.binding_name[0].kind);
}
// notify services
discoverNotifyServices(bindingMap);
// channels (NCs)
discoverChannels(bindingMap);
} catch(Exception e) {
ret = false;
}
return ret;
}
/**
* Checks the naming service for notify service instances and stores / updates them in {@link #notifyServices}.
* This update does not include the NCs owned by the notify services, so that {@link #discoverChannels(Binding[])}
* should be called after this.
* <p>
* This method is broken out from {@link #discoverNotifyServicesAndChannels()} to make it more readable.
* It should be called only from there, to keep services and NCs aligned.
*
* @param bindingMap Name service bindings in the form key = bindingName, value = bindingKind
*/
private synchronized void discoverNotifyServices(Map<String, String> bindingMap) {
ArrayList<String> newNotifyServiceNames = new ArrayList<String>(); // used for local logging only
Set<String> oldServiceIds = new HashSet<String>(notifyServices.keySet()); // used to detect services that have disappeared
for (String bindingName : bindingMap.keySet()) {
String bindingKind = bindingMap.get(bindingName);
try {
// Currently ACS does not use a unique 'kind' value when binding NotifyService instances.
// However, since COMP-9260 (ICT-575) the notify service names are guaranteed to end in "NotifyEventChannelFactory".
// There are matching monitor & control objects that start with "MC_".
// We use these naming conventions, but nonetheless call "_is_a" to double check and to catch the exception if the service is broken.
// The fact that NotifyServices have an empty kind field we use as an additional check
// to skip NCs and other objects that have a non-empty kind field and by accident use the name ending of a notify service.
if (bindingName.endsWith(NOTIFICATION_FACTORY_NAME.value) && !bindingName.startsWith("MC_") && bindingKind.isEmpty()) {
NotifyServiceData notifyService = notifyServices.get(bindingName);
if (notifyService != null) {
// We know this notify service already. Just check if it's still reachable (otherwise set flag).
// Note that we do not check here if previously unreachable services have become reachable in the meantime,
// because that would slow down a GUI refresh too much. (For that we have 'unreachableServiceChecker' running.)
if (notifyService.isReachable()) {
updateReachability(notifyService);
}
// Reachable or not, we got this notify service again from the naming service, which means it was not properly removed.
oldServiceIds.remove(bindingName);
// Todo: Should we update the notify service reference, just in case it was relocated to another machine?
}
else {
// Found a new notify service in the naming service
// We skip the manager for access to services, because since ACS 10.2 only specially registered services
// would be available in the get_service call and we want more flexibility for this special tool.
org.omg.CORBA.Object obj = resolveNotifyService(bindingName);
// We create a new NotifyServiceData object even if the service is currently unreachable,
// so that this problem becomes visible.
boolean isNewServiceReachable = isNotifyServiceReachable(obj, bindingName);
String displayName = simplifyNotifyServiceName(bindingName);
newNotifyServiceNames.add(displayName);
EventChannelFactory efact = null;
NotificationServiceMonitorControl nsmc = null;
if (isNewServiceReachable) {
efact = EventChannelFactoryHelper.narrow(obj);
try {
nsmc = resolveMonitorControl(bindingName);
} catch (Exception ex) {
m_logger.log(Level.WARNING, "Failed to obtain the MonitorControl object for service '" + bindingName + "'.");
throw ex;
}
}
NotifyServiceData notifyServiceData = new NotifyServiceData(displayName, bindingName, efact, nsmc, m_logger);
notifyServiceData.setReachable(isNewServiceReachable);
notifyServices.put(bindingName, notifyServiceData);
}
}
} catch (Exception ex) {
m_logger.log(Level.WARNING, "Failed to check / process service '" + bindingName + "'.", ex);
}
}
// Log changes for this round of service discovery
if (!newNotifyServiceNames.isEmpty()) {
Collections.sort(newNotifyServiceNames);
m_logger.info("Found " + newNotifyServiceNames.size() + " notify service instances: " + StringUtils.join(newNotifyServiceNames, ' '));
}
if (!oldServiceIds.isEmpty()) {
// These notify services were no longer listed in the naming service. This means they were properly shut down, as opposed to messed up.
String msg = "Removed " + oldServiceIds.size() + " notify service instances: ";
for (String oldServiceId : oldServiceIds) {
notifyServices.remove(oldServiceId);
msg += simplifyNotifyServiceName(oldServiceId) + ' ';
}
m_logger.info(msg);
}
}
/**
* Checks if the given corba object exists and is really a notify service.
* @param notifyServiceObj
* @param notifyServiceBindingName The naming service's binding name for our notify service (used only for a log message).
* @return <code>true</code> if the notify service exists.
* @throws AcsJIllegalArgumentEx if notifyServiceObj is reachable but not a <code>CosNotifyChannelAdmin/EventChannelFactory</code>,
* which should never happen because of the filtering we do on the name service bindings.
*/
private boolean isNotifyServiceReachable(org.omg.CORBA.Object notifyServiceObj, String notifyServiceBindingName) throws AcsJIllegalArgumentEx {
boolean ret = false;
if (notifyServiceObj != null) {
try {
if (notifyServiceObj._is_a(EventChannelFactoryHelper.id())) {
ret = true;
}
else {
AcsJIllegalArgumentEx ex = new AcsJIllegalArgumentEx();
ex.setVariable("notifyServiceObj");
ex.setValue("Type=" + notifyServiceObj.getClass().getName());
throw ex;
}
}
catch (Exception ex) {
// If the service has died, this will be a org.omg.CORBA.TRANSIENT
// TODO: Use retry/timeout config etc to not wait 10 seconds for this exception
m_logger.log(Level.SEVERE, "Notify service '" + notifyServiceBindingName + "' is not reachable.");
}
}
return ret;
}
/**
* Checks if the given NotifyServiceData references a reachable notify service,
* updates the MC reference if the service is reachable now but was not reachable last time,
* and updates the "isReachable" flag.
* @param notifyService The notify service "proxy" to check.
*/
private void updateReachability(NotifyServiceData notifyService) {
boolean wasReachableBefore = notifyService.isReachable();
boolean isReachableNow = false;
try {
org.omg.CORBA.Object serviceRef = notifyService.getEventChannelFactory();
if (serviceRef == null) {
// This happens if the notify service was unreachable when the eventGUI was started
serviceRef = resolveNotifyService(notifyService.getFactoryName());
}
isReachableNow = isNotifyServiceReachable(serviceRef, notifyService.getFactoryName());
if (isReachableNow && notifyService.getEventChannelFactory() == null) {
notifyService.updateEventChannelFactory(EventChannelFactoryHelper.narrow(serviceRef));
}
} catch (AcsJIllegalArgumentEx ex) {
// This ex cannot happen... the corba object wrapped by NotifyServiceData surely is a notify service.
} catch (Exception ex) {
}
if (isReachableNow && !wasReachableBefore) {
try {
notifyService.updateMC(resolveMonitorControl(notifyService.getFactoryName()));
} catch (Exception ex) {
// This is unexpected, because if the notify service itself is reachable, then we expect also the associated MC object to be reachable.
// We count this as "not reachable" in total.
isReachableNow = false;
}
}
notifyService.setReachable(isReachableNow);
}
/**
* Checks the naming service for NC instances and stores / updates them under the matching service from {@link #notifyServices}.
* It does not query the NCs for consumers etc. though.
* <p>
* This method is broken out from {@link #discoverNotifyServicesAndChannels(boolean)} to make it more readable.
* It should be called only from there, to keep services and NCs aligned.
*
* @param bindingMap Name service bindings in the form key = bindingName, value = bindingKind
*/
private synchronized void discoverChannels(Map<String, String> bindingMap) {
// known NC names, used to detect NCs that have disappeared.
Set<String> oldNcNames = new HashSet<String>();
for (NotifyServiceData nsData : notifyServices.values()) {
for (ChannelData channelData : nsData.getChannels()) {
oldNcNames.add(channelData.getQualifiedName());
}
}
// Note that it is useless to retrieve the NC only from the notify service (get_event_channel(ncId) etc),
// because the limited notification service API will not tell us the channel name
// even if we used the TAO extensions (where the NC names show up in the statistics but are not accessible through the API).
// However, we can get the NC names from the MC object, to be integrated with the other MCStatistics retrieval.
// Then we still need to obtain the NC corba reference separately in order to know about admin object details,
// with the NC ref coming either from the naming service or perhaps from the notify service directly if we can match ncIDs with the MC data.
for (String bindingName : bindingMap.keySet()) {
String bindingKind = bindingMap.get(bindingName);
if (bindingKind.equals(alma.acscommon.NC_KIND.value)) {
try {
String channelName = Helper.extractChannelName(bindingName);
String domainName = Helper.extractDomainName(bindingName);
// Check if we already know this NC.
ChannelData channelData = getNotifyServicesRoot().findChannel(channelName);
if (channelData != null) {
oldNcNames.remove(channelData.getQualifiedName());
channelData.setIsNewNc(false);
}
else {
m_logger.fine("New NC '" + channelName + "'.");
// A new NC. This will happen at startup, and later as NCs get added.
// The NC-to-service mapping is based on conventions and CDB data, implemented in the Helper class from jcontnc.
Helper notifyHelper = new Helper(channelName, domainName, cs, nctx);
String serviceId = notifyHelper.getNotificationFactoryNameForChannel();
NotifyServiceData service = notifyServices.get(serviceId);
if (service == null) {
// This should never happen because since ACS 12.1 we always auto-discover services as part of refreshing NCs.
// We read the service and NC bindings only once from the naming service, which avoids timing issues with NCs visible now
// even though their notify service would not have been registered before.
// Just in case we leave this code here, but log it as a warning.
discoverNotifyServices(bindingMap);
service = notifyServices.get(serviceId);
String msg = "Unknown notify service '" + simplifyNotifyServiceName(serviceId) + "' required for NC '" + channelName + "'. ";
if (service != null) {
m_logger.warning(msg + "Added the new service.");
}
else {
m_logger.warning(msg + "Failed to add the new service.");
}
}
if (service != null) {
EventChannel nc = resolveNotificationChannel(bindingName);
ChannelData cdata = new ChannelData(nc, channelName, service);
cdata.setIsNewNc(true);
// The system NCs for logging and monitor point archiving use custom event formats and cannot be subscribed to using the normal NCSubscriber.
if (domainName.equals(ACS_NC_DOMAIN_LOGGING.value) || domainName.equals(ACS_NC_DOMAIN_ARCHIVING.value) ) {
cdata.markUnsubscribable();
}
service.addChannel(channelName, cdata);
}
}
}
catch (Exception ex) {
m_logger.log(Level.WARNING, "Failed to map NC '" + bindingName + "' to its notify service.", ex);
}
}
}
// In addition to querying the name service for NCs, we also query the notify services and their MC objects directly,
// just in case there are non-standard NCs created by someone without proper naming service bindings.
for (NotifyServiceData service : notifyServices.values()) {
if (!service.isReachable()) {
// we skip services that were unreachable already before
continue;
}
// First set the missing ncId on new ChannelData objects
if (!service.getNewChannels().isEmpty()) { // check first if we can avoid the get_all_channels() remote call
int[] ncIds = service.getEventChannelFactory().get_all_channels();
for (int ncId : ncIds) {
if (service.getChannelById(ncId) == null) {
// our ncId is new... try to find matching known new NC
EventChannel newNc = null;
try {
newNc = service.getEventChannelFactory().get_event_channel(ncId);
} catch (ChannelNotFound ex) {
ex.printStackTrace(); // should never happen since we just got this Id from the service
}
ChannelData matchedNc = service.getChannelByCorbaRef(newNc);
if (matchedNc != null) {
if (matchedNc.isNewNc()) {
matchedNc.setNcId(ncId);
m_logger.fine("Service " + service.getName() + ": Matched ncId=" + ncId + " to new NC '" + matchedNc.getName() + "'.");
}
else {
m_logger.warning("Service " + service.getName() + ": Matched ncId=" + ncId + " to NC '" + matchedNc.getName() + "', but expect this NC to be new, which it isn't.");
}
}
else {
// ncId is the Id of a new NC that was not registered in the naming service (unless the corberef-based match failed).
// We don't create a ChannelData object yet, because we may get its real name below from the MC.
}
}
}
}
// Then check the service's MC object for new (hopefully named) NCs.
MCProxy mcProxy = new MCProxy(service, m_logger);
List<String> ncNamesFromMc = mcProxy.listChannels();
for (String ncNameFromMc : ncNamesFromMc) {
if (service.getChannelByName(ncNameFromMc) == null) {
// The NC was not listed in the naming service under the name we got from MC. Possibilities are:
// (a) It is not registered in the naming service at all;
// (b) It is properly registered in the naming service with its human-readable name, but it was created without using the TAO extensions
// and therefore we get only its integer ID from the MC API.
try {
// Try if we have an ID instead of a name
int ncId = Integer.parseInt(ncNameFromMc); // see NumberFormatException catch below
service.getEventChannelFactory().get_event_channel(ncId); // see ChannelNotFound catch below
// The NC was created without TAO extension name.
// Check if we know this NC already from the naming service or from a previous round here.
ChannelData channelData = service.getChannelById(ncId);
if (channelData == null) {
// First time we see this NC. It is not in the naming service and there is nowhere we can get a decent name for it.
// We simply use the Id as its name.
// If this is a problem, then this NC should be created with TAO extension name, or should be registered in the NS.
m_logger.info("Service " + service.getName() + ": Found unnamed NC with ID='" + ncId + "' that is not registered in the naming service.");
String newNcName = Integer.toString(ncId);
EventChannel unnamedNcCorbaRef = null;
try {
unnamedNcCorbaRef = service.getEventChannelFactory().get_event_channel(ncId);
} catch (ChannelNotFound ex) {
ex.printStackTrace(); // should not happen since we just got the ncId from the service
}
ChannelData newNcData = new ChannelData(unnamedNcCorbaRef, Integer.toString(ncId), service);
newNcData.markUnsubscribable();
newNcData.setIsNewNc(true);
newNcData.setNcId(ncId);
service.addChannel(newNcName, newNcData);
}
} catch (NumberFormatException ex) {
// The name was not an integer, and therefore also for sure not an ID.
// This means the NC was created using the TAO extensions, and yet it was not registered in the naming service.
m_logger.info("Service " + service.getName() + ": Found NC with name='" + ncNameFromMc + "' that is not registered in the naming service. This case was thought to not exist in practice and is therefore not supported yet by the eventGUI. Please inform ACS.");
} catch (ChannelNotFound ex) {
m_logger.warning("Strange NC '" + service.getName() + "/" + ncNameFromMc +
"' reported by MC is not listed in the naming service and has an integer name that is not a channel ID.");
}
}
else if (!bindingMap.containsKey(ncNameFromMc)) {
// The NC is not in the naming service, but was added based on MC data in a previous round.
ChannelData nc = service.getChannelByName(ncNameFromMc);
nc.setIsNewNc(false);
oldNcNames.remove(nc.getQualifiedName());
}
}
// for (String statName : nsData.getMc().get_statistic_names()) {
// System.out.println(statName);
// }
}
if (!oldNcNames.isEmpty()) {
// TODO: Change this when the eventGUI or other tools offers manual NC destruction
// Actually it could also be that an additional NotifyService with unused NCs was stopped, but in practice this does not happen.
m_logger.warning("Lost " + oldNcNames.size() + " NCs, which should not happen as we never destroy an NC even if it no longer gets used: " + StringUtils.join(oldNcNames, ' '));
for (String oldNcQualifiedName : oldNcNames) {
String oldNcName = oldNcQualifiedName.substring(oldNcQualifiedName.indexOf('#')+1);
NotifyServiceData oldNcService = getNotifyServicesRoot().findHostingService(oldNcName);
ChannelData oldNcData = oldNcService.getChannelByName(oldNcName);
closeSelectedConsumer(oldNcData);
oldNcService.removeChannel(oldNcName);
}
}
}
/**
* Resolves the notify service that is bound in the naming service with the given name.
* <p>
* This method may get called when the notify service is not reachable.
* Therefore we do not return the narrowed object, since the narrow operation makes a remote "is_a" call
* that in this case would take very long.
*/
private org.omg.CORBA.Object resolveNotifyService(String notifyBindingName)
throws CannotProceed, org.omg.CosNaming.NamingContextPackage.InvalidName, NotFound {
NameComponent[] ncomp = new NameComponent[1];
ncomp[0] = new NameComponent(notifyBindingName, "");
return nctx.resolve(ncomp);
}
/**
* Resolves the TAO monitor-control object that is bound in the naming service with the given name.
*/
private NotificationServiceMonitorControl resolveMonitorControl(String notifyBindingName)
throws CannotProceed, org.omg.CosNaming.NamingContextPackage.InvalidName, NotFound {
String name = "MC_" + notifyBindingName;
NameComponent[] ncomp = new NameComponent[1];
ncomp[0] = new NameComponent(name, "");
NotificationServiceMonitorControl nsmc = NotificationServiceMonitorControlHelper.narrow(nctx.resolve(ncomp));
return nsmc;
}
/**
* Simplifies the notify service ID by cutting off the trailing "NotifyEventChannelFactory".
* For the default NC service, whose ID is only this suffix, it returns "DefaultNotifyService".
* @param id
* @return "Logging", "Alarm", "DefaultNotifyService", "MyRealtimeNotifyService", ...
*/
private String simplifyNotifyServiceName(String id) {
String displayName = id.substring(0, id.indexOf(NOTIFICATION_FACTORY_NAME.value));
if (displayName.isEmpty()) {
displayName = "DefaultNotifyService";
}
return displayName;
}
public NotifyServices getNotifyServicesRoot() {
return new NotifyServices(notifyServices);
}
/**
* Called by NotifyServiceUpdateJob (single/periodic refresh of service summary / channel tree).
*/
public synchronized boolean getChannelStatistics() {
if(false == discoverNotifyServicesAndChannels())
{
return false;
}
for (NotifyServiceData nsData : notifyServices.values()) {
if (!nsData.isReachable()) {
// we skip services that were unreachable already in the above discoverNotifyServicesAndChannels call
continue;
}
// iterate over NCs
// TODO: Try to rely only on MC data and skip these 'get_all_consumeradmins' etc calls,
// especially if we don't want to display the admin objects as tree nodes to show consumer allocation to the shared admins.
for (ChannelData channelData : nsData.getChannels()) {
String channelName = channelData.getQualifiedName();
EventChannel ec = channelData.getCorbaRef();
int[] consAndSupp = {0,0}; // initial or previous count of consumers / suppliers
if (channelData.isNewNc()) {
lastConsumerAndSupplierCount.put(channelName, consAndSupp);
}
else if (lastConsumerAndSupplierCount.containsKey(channelName)) {
consAndSupp = lastConsumerAndSupplierCount.get(channelName);
}
// for consumers we must count the proxies, cannot just deduce their number from the consumer admins
int consumerCount = 0;
for (int consumerAdminId : ec.get_all_consumeradmins()) {
try {
ConsumerAdmin consumerAdmin = ec.get_consumeradmin(consumerAdminId);
int[] push_suppliers_ids = consumerAdmin.push_suppliers();
for(int proxyID: push_suppliers_ids) {
try {
ProxySupplier proxy = consumerAdmin.get_proxy_supplier(proxyID);
if(!NCSubscriber.AdminReuseCompatibilityHack.isDummyProxy(proxy)) {
consumerCount++;
}
} catch(ProxyNotFound ex) {
m_logger.log(AcsLogLevel.NOTICE, "Proxy with ID='" + proxyID + "' not found for consumer admin with ID='" + consumerAdminId + "', " +
"even though this Id got listed a moment ago.", ex);
}
}
} catch (AdminNotFound ex) {
ex.printStackTrace();
}
}
final String[] roleNames = {"consumer", "supplier"};
int [] proxyCounts = new int[2];
int [] proxyDeltas= new int[2];
proxyCounts[0] = consumerCount;
proxyCounts[1] = ec.get_all_supplieradmins().length; // currently for suppliers we have 1 admin object per supplier
// same code for consumer and supplier
for (int i = 0; i < proxyCounts.length; i++) {
String cstr = channelName;
int cdiff = proxyCounts[i] - consAndSupp[i];
if (cdiff != 0) {
if (cdiff > 0) {
cstr += " has added " + cdiff + " " + roleNames[i];
}
else if (cdiff < 0) {
cstr += " has removed " + (-cdiff) + " " + roleNames[i];
}
cstr += (Math.abs(cdiff)!=1 ? "s." : ".");
m_logger.info(cstr);
}
proxyDeltas[i] = cdiff;
}
lastConsumerAndSupplierCount.put(channelName, proxyCounts);
//m_logger.info("Channel: " + channelName + " has " + adminCounts[0] + " consumers and " + adminCounts[1] + " suppliers.");
channelData.setNumberConsumers(proxyCounts[0]);
channelData.setNumberSuppliers(proxyCounts[1]);
channelData.setDeltaConsumers(proxyDeltas[0]);
channelData.setDeltaSuppliers(proxyDeltas[1]);
}
}
return true;
}
/**
* Resolves a notification channel in the naming service.
*
* @return Reference to the event channel specified by channelName.
* @param bindingName
* Name of the event channel and trailing domain name, as the NC is registered with the CORBA Naming Service
* @throws AcsJException
* Standard ACS Java exception.
*/
protected EventChannel resolveNotificationChannel(String bindingName) throws AcsJException {
EventChannel retValue = null;
String nameServiceKind = alma.acscommon.NC_KIND.value;
//m_logger.info("Will call 'nctx.resolve' for binding='" + bindingName + "', kind='" + nameServiceKind + "'.");
try {
NameComponent[] t_NameSequence = { new NameComponent(bindingName, nameServiceKind) };
retValue = EventChannelHelper.narrow(nctx.resolve(t_NameSequence));
}
catch (OBJECT_NOT_EXIST ex) {
m_logger.severe("The NC '" + bindingName + "' no longer exists, probably because its notify service was restarted. The naming service still lists this NC.");
throw new AcsJUnexpectedExceptionEx(ex);
}
catch (org.omg.CosNaming.NamingContextPackage.NotFound e) {
// No other suppliers have created the channel yet
m_logger.info("The '" + bindingName + "' channel has not been created yet.");
throw new AcsJUnexpectedExceptionEx(e);
}
catch (org.omg.CosNaming.NamingContextPackage.CannotProceed e) {
// Think there is virtually no chance of this every happening but...
throw new AcsJUnexpectedExceptionEx(e);
}
catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) {
// Think there is virtually no chance of this every happening but...
throw new AcsJUnexpectedExceptionEx(e);
}
return retValue;
}
public ContainerServices getContainerServices() {
return cs;
}
private AdminConsumer getAdminConsumer(String channelName) throws AcsJException {
synchronized (consumerMap) {
if (!consumerMap.containsKey(channelName)) {
AdminConsumer adm = new AdminConsumer(channelName, cs, nctx, equeue);
adm.setPrintDetails(true);
consumerMap.put(channelName, adm);
return adm;
}
return consumerMap.get(channelName);
}
}
/**
* Called from SubscribeNCHandler.
* @throws AcsJException
*/
public void addChannelSubscription(ChannelData channelData ) throws AcsJException {
String channelName = channelData.getName();
AdminConsumer consumer = null;
synchronized (consumerMap) {
// The subscription flag and the existence of a consumer for that NC must be in sync
if (channelData.isSubscribed() != consumerMap.containsKey(channelName)) {
m_logger.warning("Inconsistent state between channel flag and consumer map. Will not subscribe to " + channelName);
return;
}
else if (channelData.isSubscribed()) {
m_logger.warning("Already subscribed to " + channelName + ". Ignoring subscription request.");
return;
}
channelData.setSubscribed(true);
consumer = getAdminConsumer(channelName);
}
consumer.startReceivingEvents();
}
/**
* TODO: Call this from mouse menu of Archiving NC instead of in the beginning.
* (It used to be called once a minute from a thread of the event list / archiving list parts.)
*
* Creates on demand an ArchiveConsumer and stores its reference in field {@link #archiveConsumer}.
*/
private synchronized void getArchiveConsumer() {
if (archiveConsumer == null) {
try {
archiveConsumer = new ArchiveConsumer(new ArchiveReceiver(archQueue), cs, nctx);
archiveConsumer.startReceivingEvents();
m_logger.info("Subscribed to monitoring/archiving events.");
} catch (AcsJException ex) {
m_logger.log(Level.WARNING, "Failed to subcribe to monitoring/archiving events.", ex);
}
}
}
public DynAnyFactory getDynAnyFactory() {
return dynAnyFactory;
}
public void closeSelectedConsumer(ChannelData channelData) {
String channelName = channelData.getName();
synchronized (consumerMap) {
if (consumerMap.containsKey(channelName)) {
AdminConsumer consumer = consumerMap.get(channelName);
try {
consumer.disconnect();
} catch (Exception ex) {
m_logger.log(Level.WARNING, "Failed to close subscriber: ", ex);
}
consumerMap.remove(channelName);
}
}
channelData.setSubscribed(false);
}
public void closeAllConsumers() {
synchronized (consumerMap) {
for (ChannelData channelData : getNotifyServicesRoot().getAllChannels()) {
if (channelData.isSubscribed()) {
closeSelectedConsumer(channelData);
}
}
}
}
/**
* TODO: Call from mouse menu handler for the archiving NC
*/
public synchronized void closeArchiveConsumer() {
if (archiveConsumer != null) {
try {
archiveConsumer.disconnect();
m_logger.info("Closed subscriber for baci monitoring events");
} catch (Exception ex) {
m_logger.log(Level.WARNING, "Got an exception disconnecting the archive consumer.", ex);
}
archiveConsumer = null;
}
}
public void tearDown() {
try {
closeAllConsumers();
closeArchiveConsumer();
unreachableServiceChecker.shutdown(10, TimeUnit.SECONDS);
acc.tearDown();
} catch (Exception ex) {
m_logger.log(Level.WARNING, "Error in EventModel#tearDown: ", ex);
}
}
/**
* Note that this method may be called very frequently, to determine whether menu items are selected/enabled, thus it must be fast.
*/
public boolean isSubscribed(ChannelData channelData) {
return channelData.isSubscribed();
}
}