/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2012
* (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.commandcenter.serviceshelper;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadFactory;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.omg.CORBA.StringHolder;
import alma.ACSErr.Completion;
import alma.acs.commandcenter.serviceshelper.TMCDBServicesHelper.AcsServiceToStart;
import alma.acs.container.corba.AcsCorba;
import alma.acs.logging.AcsLogLevel;
import alma.acs.logging.AcsLogger;
import alma.acs.util.AcsLocations;
import alma.acsdaemon.DaemonSequenceCallback;
import alma.acsdaemon.DaemonSequenceCallbackHelper;
import alma.acsdaemon.DaemonSequenceCallbackPOA;
import alma.acsdaemon.ServiceDefinitionBuilder;
import alma.acsdaemon.ServicesDaemon;
import alma.acsdaemon.ServicesDaemonHelper;
/**
* An helper to start the ACS services defined in the TMCDB with the
* services daemon.
* <P>
* The reference to the services daemon is acquired before starting/stopping services
* and released when the operation terminates.
*
* @author acaproni
*
*/
public class StartServicesHelper {
/**
* The thread class factory.
* <P>
* A new thread is created to notify listeners about the progresses
* of starting/stopping services.
*
* @author acaproni
*
*/
private class ServicesHelperThreadFactory implements ThreadFactory, UncaughtExceptionHandler {
/**
* The thread group to which belongs all the thread
* created by the factory
*/
private final ThreadGroup threadGroup;
/**
* C'tor
*
* @param name The name of this factory (used to name the thread group)
*/
public ServicesHelperThreadFactory(String name) {
if (name==null || name.isEmpty()) {
throw new IllegalArgumentException("Invalig name: "+name);
}
threadGroup = new ThreadGroup(name);
}
/**
* Create a new thread assigned to the {@link #threadGroup} thread group.
* Each thread is a daemon thread.
*
* @param r The runnable
* @return The newly created thread
*
* @see ThreadFactory
*/
@Override
public Thread newThread(Runnable r) {
if (r==null) {
throw new IllegalArgumentException("Invalid NULL runnable!");
}
Thread t= new Thread(threadGroup, r);
t.setDaemon(true);
t.setUncaughtExceptionHandler(this);
return t;
}
/**
* Create a new thread with the given name.
* <P>
* The creation of the thread is delegated to {@link #newThread(Runnable)}.
*
* @param r The runnable
* @param name The name of the thread
* @return The newly created thread
*/
public Thread newThread(Runnable r, String name) {
Thread t = newThread(r);
t.setName(name);
return t;
}
/**
* It is executed in case one of the thread of the group terminates with an exception.
* <P>
* It logs the exception to help debugging.
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("Thread "+t.getName()+" terminated with uncaught exception: "+e.getMessage());
e.printStackTrace();
logger.log(AcsLogLevel.ERROR,"Thread "+t.getName()+" terminated with uncaught exception" , e);
}
}
/**
* The callback to notify the listeners about the progress of the async start/stop
* of services.
*
* @author acaproni
*
*/
public class DaemonSequenceCallbackImpl extends DaemonSequenceCallbackPOA
{
/**
* The logger
*/
private final AcsLogger logger;
/**
* <code>true</code> if the callback is used while starting services
*/
private final boolean startingServices;
/**
* C'tor.
*
* @param logger The logger
* @param starting <code>true</code> means that the callback is used while starting services
*/
public DaemonSequenceCallbackImpl(AcsLogger logger, boolean starting) {
if (logger==null) {
throw new IllegalArgumentException("The logger can't be null");
}
this.logger = logger;
this.startingServices=starting;
}
public void done(Completion comp) {
logger.log(AcsLogLevel.DEBUG,"Done: comp=" + comp.timeStamp);
notifyListenersDone(comp.type, comp.code);
}
public void working(String service, String host, short instance_number, Completion comp) {
logger.log(AcsLogLevel.DEBUG,"Working: service=" + service + " host=" + host + " instance=" + instance_number + " comp=" + comp.timeStamp);
notifyListenersStartStop(startingServices, service, host, instance, comp.type, comp.code);
}
/**
* Notify all the listeners about the progresses while starting/stopping of services.
* <P>
* This method is executed by {@link #working(String, String, short, Completion)}
* to notify the listener that a service started or stopped.
*
* @param starting <code>true</code> means starting of services;
* <code>false</code> means stopping.
* @param serviceName The name of the service.
* @param hostName The name of the host where the service started.
* @param instance The number of the ACS instance
* @param errorType The type of error (0 means no error)
* @param errorCode The code of the error
*/
private void notifyListenersStartStop(
final boolean starting,
final String serviceName,
final String hostName,
final int instance,
final int errorType,
final int errorCode) {
if (listeners.isEmpty()) {
// Nobody to notify!
return;
}
Thread listenersUpdaterThread = threadFactory.newThread(new Runnable() {
public void run() {
synchronized (listeners) {
for (ServicesListener listener: listeners) {
// The working method of the callback has been invoked
try {
if (starting) {
listener.serviceStarted(
serviceName,
hostName,
instance,
errorType,errorCode);
} else {
listener.serviceStopped(
serviceName,
hostName,
instance,
errorType,
errorCode);
}
} catch (Throwable t) {
System.err.println("Exception caught notifying working to listener "+t.getMessage());
t.printStackTrace();
logger.log(AcsLogLevel.ERROR, "Exception caught notifying working to listener", t);
}
}
}
}
},"notifyWorking "+serviceName+"@"+hostName);
listenersUpdaterThread.setName("ListenersUpdaterStartStopThread");
listenersUpdaterThread.setDaemon(true);
listenersUpdaterThread.start();
}
/**
* Notify all the listeners that the operation has terminated.
* <P>
* This method is executed by {@link #done(Completion)}
* to notify the listener that the starting of stopping of the
* services terminated.
*
* @param errorType The type of error (0 means no error)
* @param errorCode The code of the error
*/
private void notifyListenersDone(
final int errorType,
final int errorCode) {
if (listeners.isEmpty()) {
// Nobody to notify!
return;
}
Thread listenersUpdaterThread = threadFactory.newThread(new Runnable() {
public void run() {
synchronized (listeners) {
for (ServicesListener listener: listeners) {
try {
listener.done(errorType, errorCode);
} catch (Throwable t) {
System.err.println("Exception caught notifying done to listener "+t.getMessage());
t.printStackTrace();
logger.log(AcsLogLevel.ERROR, "Exception caught notifying done to listener", t);
}
}
}
}
},"notifyDone");
listenersUpdaterThread.setName("ListenersUpdaterDoneThread");
listenersUpdaterThread.setDaemon(true);
listenersUpdaterThread.start();
}
}
/**
* An object to hold the definitions of the services to start.
*
* @author acaproni
*
*/
public class AlarmServicesDefinitionHolder {
/**
* The XML describing the services to start/stop
*/
public final String xmlServicesDefinition;
/**
* The immutable list of services to start as it has been retrieved
* from the TMCDB
*/
public final List<AcsServiceToStart> services;
/**
* Constructor
*
* @param xml The xml describing the services to start/stop
* @param services The list of services to start read from the TMCDB
*/
public AlarmServicesDefinitionHolder(String xml, List<AcsServiceToStart> services) {
xmlServicesDefinition=xml;
this.services=services;
}
}
/**
* The listener of events generated starting and stopping
* the services.
*
* @author acaproni
*
*/
public interface ServicesListener {
/**
* Invoked when start/stop of services terminated.
*
* @param errorType The type of error (0 means no error)
* @param errorCode The code of the error
*/
public void done(int errorType, int errorCode);
/**
* Invoked when the service with the passed name has been
* successfully started.
*
* @param name The name of the service
* @param host The name of the host where the services has been started/stopped
* @param instance The number of the ACS instance
* @param errorType The type of error (0 means no error)
* @param errorCode The code of the error
*/
public void serviceStarted(String name, String host, int instance, int errorType, int errorCode);
/**
* Invoked when the service with the passed name has been
* successfully stopped.
*
* @param name The name of the service
* @param host The name of the host where the services has been started/stopped
* @param instance The number of the ACS instance
* @param errorType The type of error (0 means no error)
* @param errorCode The code of the error
*/
public void serviceStopped(String name, String host, int instance, int errorType, int errorCode);
}
/**
* The exception returned when an error happens getting the services daemon.
*
* @author acaproni
*
*/
public class GettingDaemonException extends Exception {
/**
* C'tor
*
* @see Exception#Exception(String)
*/
public GettingDaemonException(String errorStr) {
super(errorStr);
}
/**
* C'tor
*
* @see Exception#Exception(String, Throwable)
*/
public GettingDaemonException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Encapsulate the exception returned by the daemon
*
*
* @author acaproni
*
*/
public class DaemonException extends Exception {
/**
* C'tor
*
* @see Exception#Exception(String)
*/
public DaemonException(String errorStr) {
super(errorStr);
}
/**
* C'tor
*
* @see Exception#Exception(String, Throwable)
*/
public DaemonException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* The exception in case of error from the TMCDB
*
*
* @author acaproni
*
*/
public class TMCDBException extends Exception {
/**
* C'tor
*
* @see Exception#Exception(String)
*/
public TMCDBException(String errorStr) {
super(errorStr);
}
/**
* C'tor
*
* @see Exception#Exception(String, Throwable)
*/
public TMCDBException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* The listener of events
*/
private final Set<ServicesListener> listeners =
Collections.synchronizedSet(new HashSet<ServicesListener>());
/**
* The session to access the TMCDB
*/
private final Session session;
/**
* The logger
*/
private final AcsLogger logger;
/**
* The name of the configuration to get the list of services from
* the tmcdb
*/
private final String configurationName;
/**
* The ContainerServices
*/
private final AcsCorba acsCorba;
/**
* The host where the service daemon runs
*/
private final String daemonHost;
/**
* The ACS instance
*/
private final int instance;
/**
* The thread factory
*/
private final ServicesHelperThreadFactory threadFactory = new ServicesHelperThreadFactory("StartServicesHelperThreadGroup");
/**
* Constructor.
*
* @param Session The hibernate session to read data from the TMCDB
* @param configurationName The name of the configuration
* @param acsCorba The {@link AcsCorba}
* @param logger The logger
* @param daemonHost The host where the service daemon runs
* @param instance The ACS instance
*/
public StartServicesHelper(
Session session,
String configurationName,
AcsCorba acsCorba,
AcsLogger logger,
String daemonHost,
int instance) {
if (session==null) {
throw new IllegalArgumentException("The name of the configuration can't be null nor empty!");
}
this.session=session;
if (configurationName==null || configurationName.isEmpty()) {
throw new IllegalArgumentException("The Session can't be NULL nor empty!");
}
this.configurationName=configurationName;
if (acsCorba==null) {
throw new IllegalArgumentException("The AcsCorba can't be null!");
}
this.acsCorba=acsCorba;
if (logger==null) {
throw new IllegalArgumentException("The logger can't be null!");
}
this.logger=logger;
if (daemonHost==null || daemonHost.isEmpty()) {
throw new IllegalArgumentException("The host address of the service daemon can't be NULL nor empty!");
}
this.daemonHost=daemonHost;
this.instance=instance;
logger.log(AcsLogLevel.DEBUG,"StartServicesHelper instantiated with daemon "+this.daemonHost+", instance "+this.instance+" and configuration name "+this.configurationName);
}
/**
* Reads from the TMCDB the list of ACS services to start.
* The definition of the services and the host where we want to deploy them
* is sent to an acs daemon in order to build an XML representation
* that can be later used by the daemons to start all the services.
* <P>
* Using this method is optional and expected only for special cases (in conjunction with {@link #startACSServices}),
* because method {@link #startACSServices} includes this step.
* <P>
* This method delegates to {@link #internalGetServicesDescritpion(ServicesDaemon, ServiceDefinitionBuilder)}
* @return A struct with the XML representation of the services to start and the services to start read from the TMCDB
* @throws GettingDaemonException In case of error getting the daemon
* @throws DaemonException In case of error from the daemon
* @throws TMCDBException If the list of service read from the TMCDB is empty
*
* @see #internalGetServicesDescritpion(ServicesDaemon, ServiceDefinitionBuilder)
*/
public AlarmServicesDefinitionHolder getServicesDescription()
throws GettingDaemonException, DaemonException, TMCDBException {
// Get the reference to the daemon
ServicesDaemon daemon = getServicesDaemon();
return internalGetServicesDescription(daemon);
}
/**
* Return the the XML string describing the services to start by reading the services from the
* TMCDB.
*
* @param daemon That services daemon
* @return The XML and the list of services describing the services to start
* @throws HibernateException In case of error reading the services from the TMCDB
* @throws DaemonException In case of error from the daemon
* @throws TMCDBException If the list of service read from the TMCDB is empty
*/
private AlarmServicesDefinitionHolder internalGetServicesDescription (
ServicesDaemon daemon)
throws HibernateException, DaemonException, TMCDBException {
if (daemon==null) {
throw new IllegalArgumentException("The daemon can't be null");
}
// Get the service definition builder for the current instance
ServiceDefinitionBuilder srvDefBuilder=null;
try {
srvDefBuilder=daemon.create_service_definition_builder((short)instance);
} catch (Throwable t) {
throw new DaemonException("Error getting the service definition builder", t);
}
logger.log(AcsLogLevel.DEBUG,"ServiceDefinitionBuilder got from the ACS services daemon");
// Get the services from the TMCDB
List<AcsServiceToStart> services = getServicesList();
if (services.isEmpty()) {
throw new TMCDBException("No services to start from TMCDB");
}
logger.log(AcsLogLevel.DEBUG,"Read "+services.size()+" to start from TMCDB");
// Add the services to the service definition builder
try {
/*
* NOTE: some of the paremeters required to start the services are hardcoded
* because the tables of the database do not allow to have them stored
* in the TMCDB.
* For this release it is good enough but if we want to have them available
* in the TMCDB and the explorer then we have to change the table of
* the database too.
*/
for (AcsServiceToStart svc: services) {
if (svc.serviceName==null) {
logger.log(AcsLogLevel.DEBUG,"Adding "+svc.serviceType+"@"+svc.hostName+" to the ServicesDefinitionBuilder");
} else {
logger.log(AcsLogLevel.DEBUG,"Adding "+svc.serviceType+"@"+svc.hostName+" with name "+svc.serviceName+" to the ServicesDefinitionBuilder");
}
switch (svc.serviceType) {
case MANAGER: {
srvDefBuilder.add_manager(svc.hostName, "", false);
break;
}
case ALARM: {
srvDefBuilder.add_alarm_service(svc.hostName);
break;
}
case CDB: {
srvDefBuilder.add_rdb_cdb(svc.hostName, false, configurationName);
break;
}
case IFR: {
srvDefBuilder.add_interface_repository(svc.hostName, true, false);
break;
}
case LOGGING: {
srvDefBuilder.add_logging_service(svc.hostName, "Log");
break;
}
case LOGPROXY: {
srvDefBuilder.add_acs_log(svc.hostName);
break;
}
case NAMING: {
srvDefBuilder.add_naming_service(svc.hostName);
break;
}
case NOTIFICATION: {
srvDefBuilder.add_notification_service(svc.serviceName, svc.hostName);
break;
}
default: {
throw new Exception("Unknown type of service to start: "+svc.serviceType+", on "+svc.hostName);
}
}
}
} catch (Throwable t) {
throw new DaemonException("Error adding services to the daemon", t);
}
logger.log(AcsLogLevel.DEBUG,"All the services have been added to the ServiceDefinitionBuilder");
String svcsXML = srvDefBuilder.get_services_definition();
StringHolder errorStr = new StringHolder();
if (!srvDefBuilder.is_valid(errorStr)) {
// Error in the XML
throw new DaemonException("Error in the services definition: "+errorStr.value);
} else {
logger.log(AcsLogLevel.DEBUG,"Services successfully validated by the ServicesDefinitionBuilder");
}
return new AlarmServicesDefinitionHolder(svcsXML, Collections.unmodifiableList(services));
}
/**
* Starts the services whose XML definition is in the parameter.
* <P>
* The real starting of services is delegated to {@link #internalStartServices(ServicesDaemon, ServiceDefinitionBuilder, String)}
*
* The starting of the services is delegated to a acs service daemon.
* @param xmlListOfServices The XML describing the list of services to start.
* It is generally returned by getServicesDescription()
*
* @throws GettingDaemonException in case of error getting the services daemon
* @throws DaemonException In case of error from the daemon
*/
public void startACSServices(String xmlListOfServices)
throws GettingDaemonException, DaemonException {
if (xmlListOfServices==null || xmlListOfServices.isEmpty()) {
throw new IllegalArgumentException("The XML list of services can't be null nor empty");
}
// Get the reference to the daemon
ServicesDaemon daemon = getServicesDaemon();
// Get the service definition builder for the current instance
ServiceDefinitionBuilder srvDefBuilder=null;
try {
srvDefBuilder=daemon.create_service_definition_builder((short)instance);
} catch (Throwable t) {
throw new DaemonException("Error getting the service definition builder", t);
}
logger.log(AcsLogLevel.DEBUG,"ServiceDefinitionBuilder got from the ACS services daemon");
try {
srvDefBuilder.add_services_definition(xmlListOfServices);
} catch (Throwable t) {
throw new DaemonException("Error adding the list of services to the daemon", t);
}
StringHolder errorStr = new StringHolder();
if (!srvDefBuilder.is_valid(errorStr)) {
// Error in the XML
throw new DaemonException("Invalid XML list of services: "+errorStr.value);
}
internalStartServices(daemon, xmlListOfServices);
}
/**
* Starts the services as they are defined in the TMCDB.
* This method is a convenience that before first gets the list of services from the TMCDB
* by calling getServicesDescription() and then executes startACSServices(String xmlListOfServices)
* <P>
* The real starting of services is delegated to {@link #internalStartServices(ServicesDaemon, ServiceDefinitionBuilder, String)}
*
* @return A struct with the An XML representation of the started services, to be used in {@link #stopServices}
* and a list of services as they have been read from the TMCDB
*
* @throws GettingDaemonException in case of error getting the services daemon
* @throws HibernateException In case of error reading the services from the TMCDB
* @throws DaemonExceptionIn case of error from the daemon
* @throws TMCDBException If the list of services read from the TMCDB is empty
* @see AlarmServicesDefinitionHolder
*/
public AlarmServicesDefinitionHolder startACSServices() throws
GettingDaemonException, HibernateException, DaemonException, TMCDBException {
logger.log(AcsLogLevel.DEBUG,"Starting ACS with services daemon");
// Get the reference to the daemon
ServicesDaemon daemon = getServicesDaemon();
logger.log(AcsLogLevel.DEBUG,"Services daemon acquired");
// Get the services from the TMCDB
AlarmServicesDefinitionHolder holder=internalGetServicesDescription(daemon);
// Start the services
internalStartServices(daemon, holder.xmlServicesDefinition);
return holder;
}
/**
* Asks the daemon to start the services whose description is in the passed XML.
* <P>
* This method assumes that the XML has already been validated
* by {@link ServiceDefinitionBuilder#is_valid(StringHolder)}
*
* @param daemon The daemon to start the services
* @param builder The builder to validate the XML;
* If <code>null</code> an instance is retrieved from the daemon.
* @param svcsXML The XML string describing the services to start
*
* @throws DaemonException In case of a bad parameter in the method to start the services
* or instantiating the callback
*/
private void internalStartServices(
ServicesDaemon daemon,
String svcsXML) throws DaemonException {
if (daemon==null) {
throw new IllegalArgumentException("The daemon can't be null");
}
if (svcsXML==null || svcsXML.isEmpty()) {
throw new IllegalArgumentException("The XML list of services can't be null nor empty");
}
DaemonSequenceCallbackImpl callback = new DaemonSequenceCallbackImpl(logger,true);
DaemonSequenceCallback daemonSequenceCallback = null;
try {
daemonSequenceCallback = DaemonSequenceCallbackHelper.narrow(acsCorba.activateOffShoot(callback, acsCorba.getRootPOA()));
} catch (Throwable t) {
throw new DaemonException("Error starting the callback", t);
}
logger.log(AcsLogLevel.DEBUG,"Asking the services daemon to start services");
try {
daemon.start_services(svcsXML, true, daemonSequenceCallback);
} catch (Throwable t) {
throw new DaemonException("Error starting the services", t);
}
}
/**
* Stops the services whose definition is in the passed parameter.
*
* @param xmlListOfServices The XML describing the list of services to stop.
* @throws GettingDaemonException In case of error getting the services daemon
* @throws DaemonException In case of error from the services daemon
*/
public void stopServices(String xmlListOfServices)
throws GettingDaemonException, DaemonException {
if (xmlListOfServices==null || xmlListOfServices.isEmpty()) {
throw new IllegalArgumentException("The XML list of services can't be null nor empty");
}
logger.log(AcsLogLevel.DEBUG,"Stopping ACS with the services daemon");
// Get the reference to the daemon
ServicesDaemon daemon = getServicesDaemon();
DaemonSequenceCallbackImpl callback = new DaemonSequenceCallbackImpl(logger,false);
DaemonSequenceCallback daemonSequenceCallback = null;
try {
daemonSequenceCallback = DaemonSequenceCallbackHelper.narrow(acsCorba.activateOffShoot(callback, acsCorba.getRootPOA()));
} catch (Throwable t) {
throw new DaemonException("Error instantiating the callback", t);
}
logger.log(AcsLogLevel.DEBUG,"Asking the services daemon to stop services");
try {
daemon.stop_services(xmlListOfServices, daemonSequenceCallback);
} catch (Throwable t) {
throw new DaemonException("Error stopping services", t);
}
}
/**
* Add a listener to be notified about the progress of the start/stop of services.
*
* @param listener The not <code>null</code> listener to add.
*/
public void addServiceListener(ServicesListener listener) {
if (listener==null) {
throw new IllegalArgumentException("The listener can't be null");
}
listeners.add(listener);
}
/**
* Remove the passed listener of the start/stop of services.
*
* @param listener The not <code>null</code> listener to remove.
* @return <code>true</code> if this set contained the specified element
*/
public boolean removeServiceListener(ServicesListener listener) {
if (listener==null) {
throw new IllegalArgumentException("The listener can't be null");
}
return listeners.remove(listener);
}
/**
* Get the list of services to start from the TMCDB by delegating to {@link TMCDBServicesHelper}
* @return The list of services to start.
* @throws Exception In case of error getting the list of services from the TMCDB
*/
private List<AcsServiceToStart> getServicesList() throws HibernateException {
TMCDBServicesHelper tmcdbHelper = new TMCDBServicesHelper(logger, session);
return tmcdbHelper.getServicesList(configurationName);
}
/**
* Connects to the services daemon building the corba loc from its
* host address in ({@link #daemonHost}.
*
* @return the reference to the services daemon
*
* @throws GettingDaemonException in case of error getting the services daemon
*/
private ServicesDaemon getServicesDaemon() throws GettingDaemonException {
ServicesDaemon daemon;
String daemonLoc = AcsLocations.convertToServicesDaemonLocation(daemonHost);
logger.log(AcsLogLevel.DEBUG,"Getting services daemon from "+daemonHost);
try {
org.omg.CORBA.Object object =
acsCorba.getORB().string_to_object(daemonLoc);
daemon = ServicesDaemonHelper.narrow(object);
if (daemon == null)
throw new GettingDaemonException("Received null trying to retrieve acs services daemon on "+daemonHost);
if (daemon._non_existent()) // this may be superfluous with daemons but shouldn't hurt either
throw new GettingDaemonException("Acs services daemon not existing on "+daemonHost);
} catch (Throwable t) {
throw new GettingDaemonException("Error getting the services daemon "+t.getMessage(), t);
}
return daemon;
}
}