/** * Copyright 2013-2017 Linagora, Université Joseph Fourier, Floralis * * The present code is developed in the scope of the joint LINAGORA - * Université Joseph Fourier - Floralis research program and is designated * as a "Result" pursuant to the terms and conditions of the LINAGORA * - Université Joseph Fourier - Floralis research program. Each copyright * holder of Results enumerated here above fully & independently holds complete * ownership of the complete Intellectual Property rights applicable to the whole * of said Results, and may freely exploit it in any manner which does not infringe * the moral rights of the other copyright holders. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.roboconf.dm.management; import java.io.IOException; import java.util.List; import java.util.Objects; import java.util.Timer; import java.util.logging.Level; import java.util.logging.Logger; import net.roboconf.core.Constants; import net.roboconf.core.model.helpers.InstanceHelpers; import net.roboconf.core.runtime.IReconfigurable; import net.roboconf.core.utils.Utils; import net.roboconf.dm.internal.api.IRandomMngr; import net.roboconf.dm.internal.api.impl.ApplicationMngrImpl; import net.roboconf.dm.internal.api.impl.ApplicationTemplateMngrImpl; import net.roboconf.dm.internal.api.impl.AutonomicMngrImpl; import net.roboconf.dm.internal.api.impl.CommandsMngrImpl; import net.roboconf.dm.internal.api.impl.ConfigurationMngrImpl; import net.roboconf.dm.internal.api.impl.DebugMngrImpl; import net.roboconf.dm.internal.api.impl.InstancesMngrImpl; import net.roboconf.dm.internal.api.impl.MessagingMngrImpl; import net.roboconf.dm.internal.api.impl.NotificationMngrImpl; import net.roboconf.dm.internal.api.impl.PreferencesMngrImpl; import net.roboconf.dm.internal.api.impl.RandomMngrImpl; import net.roboconf.dm.internal.api.impl.TargetConfiguratorImpl; import net.roboconf.dm.internal.api.impl.TargetHandlerResolverImpl; import net.roboconf.dm.internal.api.impl.TargetsMngrImpl; import net.roboconf.dm.internal.environment.messaging.DmMessageProcessor; import net.roboconf.dm.internal.environment.messaging.RCDm; import net.roboconf.dm.internal.tasks.CheckerForHeartbeatsTask; import net.roboconf.dm.internal.tasks.CheckerForStoredMessagesTask; import net.roboconf.dm.internal.tasks.CheckerForTargetsConfigurationTask; import net.roboconf.dm.internal.utils.ConfigurationUtils; import net.roboconf.dm.jmx.ManagerMBean; import net.roboconf.dm.management.api.IApplicationMngr; import net.roboconf.dm.management.api.IApplicationTemplateMngr; import net.roboconf.dm.management.api.IAutonomicMngr; import net.roboconf.dm.management.api.ICommandsMngr; import net.roboconf.dm.management.api.IConfigurationMngr; import net.roboconf.dm.management.api.IDebugMngr; import net.roboconf.dm.management.api.IInstancesMngr; import net.roboconf.dm.management.api.IMessagingMngr; import net.roboconf.dm.management.api.INotificationMngr; import net.roboconf.dm.management.api.IPreferencesMngr; import net.roboconf.dm.management.api.ITargetHandlerResolver; import net.roboconf.dm.management.api.ITargetsMngr; import net.roboconf.dm.management.events.IDmListener; import net.roboconf.messaging.api.business.ListenerCommand; import net.roboconf.messaging.api.factory.IMessagingClientFactory; import net.roboconf.messaging.api.factory.MessagingClientFactoryRegistry; import net.roboconf.target.api.TargetHandler; /** * This class acts as a front-end to access the various features of the DM. * <p> * This class is designed to work with OSGi, iPojo and Admin Config.<br> * But it can also be used programmatically. * </p> * <pre><code> * // Configure * Manager manager = new Manager(); * manager.setMessagingType( "rabbitmq" ); * * // Change the way we resolve handlers for deployment targetsMngr * manager.setTargetResolver( ... ); * * // Connect to the messaging server * manager.start(); * </code></pre> * * @author Noël - LIG * @author Pierre-Yves Gibello - Linagora * @author Vincent Zurczak - Linagora * @author Pierre Bourret - Université Joseph Fourier */ public class Manager implements IReconfigurable, ManagerMBean { // Constants private static final long TIMER_PERIOD = 6000; // Injected by iPojo or Admin Config protected String messagingType; protected String domain = Constants.DEFAULT_DOMAIN; protected IPreferencesMngr preferencesMngr; // Internal fields protected final Logger logger = Logger.getLogger( getClass().getName()); protected Timer timer; private RCDm messagingClient; // API access private final NotificationMngrImpl notificationMngr; private final MessagingMngrImpl messagingMngr; private final ApplicationMngrImpl applicationMngr; private final InstancesMngrImpl instancesMngr; private final IConfigurationMngr configurationMngr; private final IApplicationTemplateMngr applicationTemplateMngr; private final ITargetsMngr targetsMngr; private final IDebugMngr debugMngr; private final ICommandsMngr commandsMngr; private final IAutonomicMngr autonomicMngr; private final TargetHandlerResolverImpl defaultTargetHandlerResolver; // Private API private final IRandomMngr randomMngr; private final TargetConfiguratorImpl targetConfigurator; /** * Constructor. */ public Manager() { super(); // Home-made DI. // We do not want to mix N frameworks. this.notificationMngr = new NotificationMngrImpl(); this.configurationMngr = new ConfigurationMngrImpl(); this.randomMngr = new RandomMngrImpl(); this.messagingMngr = new MessagingMngrImpl(); this.defaultTargetHandlerResolver = new TargetHandlerResolverImpl(); this.targetsMngr = new TargetsMngrImpl( this.configurationMngr ); this.debugMngr = new DebugMngrImpl( this.messagingMngr, this.notificationMngr ); this.commandsMngr = new CommandsMngrImpl( this ); this.autonomicMngr = new AutonomicMngrImpl( this.commandsMngr ); this.applicationMngr = new ApplicationMngrImpl( this.notificationMngr, this.configurationMngr, this.targetsMngr, this.messagingMngr, this.randomMngr, this.autonomicMngr ); this.applicationTemplateMngr = new ApplicationTemplateMngrImpl( this.notificationMngr, this.targetsMngr, this.applicationMngr, this.configurationMngr ); this.applicationMngr.setApplicationTemplateMngr( this.applicationTemplateMngr ); this.targetConfigurator = new TargetConfiguratorImpl(); this.targetConfigurator.setTargetHandlerResolver( this.defaultTargetHandlerResolver ); this.instancesMngr = new InstancesMngrImpl( this.messagingMngr, this.notificationMngr, this.targetsMngr, this.randomMngr, this.targetConfigurator ); this.instancesMngr.setTargetHandlerResolver( this.defaultTargetHandlerResolver ); this.instancesMngr.setRuleBasedHandler( this.autonomicMngr ); this.instancesMngr.setDmDomain( this.domain ); // The manager is supposed to be an API. // To make it simple to use in non-OSGi environments, we instantiate a default set of preferences. // This will prevent NPEs. In OSGi environments, iPojo will override it. setPreferencesMngr( new PreferencesMngrImpl()); } // iPojo stuff /** * Starts the manager. * <p> * It is invoked by iPojo when an instance becomes VALID. * </p> */ public void start() { this.logger.info( "The DM is about to be launched." ); // Start the messaging DmMessageProcessor messageProcessor = new DmMessageProcessor( this ); this.messagingClient = new RCDm( this.applicationMngr ); this.messagingClient.setDomain( this.domain ); this.messagingClient.associateMessageProcessor( messageProcessor ); this.messagingMngr.setMessagingClient( this.messagingClient ); // Start the target configurator this.targetConfigurator.start(); // Run the timer this.timer = new Timer( "Roboconf's Management Timer", false ); this.timer.scheduleAtFixedRate( new CheckerForStoredMessagesTask( this.applicationMngr, this.messagingMngr ), 0, TIMER_PERIOD ); this.timer.scheduleAtFixedRate( new CheckerForTargetsConfigurationTask( this.targetConfigurator ), 0, TIMER_PERIOD ); this.timer.scheduleAtFixedRate( new CheckerForHeartbeatsTask( this.applicationMngr, this.notificationMngr ), 0, Constants.HEARTBEAT_PERIOD ); // Configure the messaging reconfigure(); // Restore what is necessary this.applicationTemplateMngr.restoreTemplates(); this.applicationMngr.restoreApplications(); // We must update instance states after we restored applications restoreAllInstances(); // Enable notifications to listeners this.notificationMngr.enableNotifications(); this.logger.info( "The DM was launched." ); } /** * Stops the manager. * <p> * It is invoked by iPojo when an instance becomes INVALID. * </p> */ public void stop() { // Cancel the timer this.logger.info( "The DM is about to be stopped." ); if( this.timer != null ) { this.timer.cancel(); this.timer = null; } // Save the instances for( ManagedApplication ma : this.applicationMngr.getManagedApplications()) ConfigurationUtils.saveInstances( ma ); // Disable notifications to listeners this.notificationMngr.disableNotifications(); // Stop the target configurator this.targetConfigurator.stop(); // Stops listening to the debug queue. if( this.messagingClient != null ) { try { this.messagingClient.listenToTheDm( ListenerCommand.STOP ); } catch ( IOException e ) { this.logger.log( Level.WARNING, "Cannot stop to listen to the debug queue", e ); } this.messagingClient.getMessageProcessor().stopProcessor(); this.messagingClient.getMessageProcessor().interrupt(); try { this.messagingClient.closeConnection(); } catch( IOException e ) { this.logger.warning( "The messaging client could not be terminated correctly. " + e.getMessage()); Utils.logException( this.logger, e ); } } this.logger.info( "The DM was stopped." ); } /** * This method is invoked by iPojo every time a new target handler appears. * @param targetItf the appearing target handler */ public void targetAppears( TargetHandler targetItf ) { this.defaultTargetHandlerResolver.addTargetHandler( targetItf ); // When a target is deployed, we may also have to update instance states. // Consider as an example when the DM restarts. Targets may be injected // before and after the pojo was started by iPojo. // See #519 for more details. // Notice we restore instances only when the DM was started (the messaging // must be ready). If it is not started, do nothing. The "start" method // will trigger the restoration. // We consider the DM is started if the timer is not null. if( this.timer != null ) restoreInstancesFrom( targetItf ); } /** * This method is invoked by iPojo every time a target handler disappears. * @param targetItf the disappearing target handler */ public void targetDisappears( TargetHandler targetItf ) { this.defaultTargetHandlerResolver.removeTargetHandler( targetItf ); } /** * This method is invoked by iPojo every time a DM listener appears. * @param targetItf the appearing listener */ public void listenerAppears( IDmListener listener ) { this.notificationMngr.addListener( listener ); } /** * This method is invoked by iPojo every time a DM listener disappears. * @param listener the disappearing listener */ public void listenerDisappears( IDmListener listener ) { this.notificationMngr.removeListener( listener ); } // Reconfiguration /** * This method reconfigures the manager. * <p> * It is NOT invoked DIRECTLY by iPojo anymore. * </p> */ @Override public void reconfigure() { // Update the messaging client this.logger.info( "Reconfiguration requested in the DM." ); if( this.messagingClient != null ) { this.messagingClient.setDomain( this.domain ); this.messagingClient.switchMessagingType( this.messagingType ); try { if( this.messagingClient.isConnected()) this.messagingClient.listenToTheDm( ListenerCommand.START ); } catch ( IOException e ) { this.logger.log( Level.WARNING, "Cannot start to listen to the debug queue", e ); } } // We must update instance states after we switched the messaging configuration. restoreAllInstances(); this.logger.info( "The DM was successfully (re)configured." ); } // Setters /** * Sets the messaging type. * <p> * If the set messaging type is different than the previous one, * this method triggers a re configuration. * </p> * * @param messagingType the messaging type */ public void setMessagingType( String messagingType ) { // Properties are injected on every modification. // So, we just want to track changes. // We only want to reconfigure the messaging client // when the messaging type changes. if( ! Objects.equals( this.messagingType, messagingType )) { this.messagingType = messagingType; this.logger.fine( "Messaging type set to " + this.messagingType ); // Explicitly require a reconfiguration. reconfigure(); } } /** * @param domain the domain to set */ public void setDomain( String domain ) { // Properties are injected on every modification. // So, we just want to track changes. // We only want to reconfigure the messaging client // when the domain changes. if( ! Objects.equals( this.domain, domain )) { this.domain = domain; this.logger.fine( "Domain set to " + domain ); this.instancesMngr.setDmDomain( domain ); // Explicitly require a reconfiguration. reconfigure(); } } /** * @param preferencesMngr the preferencesMngr to set */ public void setPreferencesMngr( IPreferencesMngr preferencesMngr ) { this.preferencesMngr = preferencesMngr; ((RandomMngrImpl) this.randomMngr).setPreferencesMngr( preferencesMngr ); ((AutonomicMngrImpl) this.autonomicMngr).setPreferencesMngr( preferencesMngr ); } /** * Sets the target resolver. * @param targetHandlerResolver a resolver for target handlers */ public void setTargetResolver( ITargetHandlerResolver targetHandlerResolver ) { if( targetHandlerResolver == null ) { this.targetConfigurator.setTargetHandlerResolver( this.defaultTargetHandlerResolver ); this.instancesMngr.setTargetHandlerResolver( this.defaultTargetHandlerResolver ); } else { this.targetConfigurator.setTargetHandlerResolver( targetHandlerResolver ); this.instancesMngr.setTargetHandlerResolver( targetHandlerResolver ); } } // Getters /** * @return the domain */ public String getDomain() { return this.domain; } /** * @return the notification API */ public INotificationMngr notificationMngr() { return this.notificationMngr; } /** * @return the messaging API */ public IMessagingMngr messagingMngr() { return this.messagingMngr; } /** * @return the applications management API */ public IApplicationMngr applicationMngr() { return this.applicationMngr; } /** * @return the instances management API */ public IInstancesMngr instancesMngr() { return this.instancesMngr; } /** * @return the configuration API */ public IConfigurationMngr configurationMngr() { return this.configurationMngr; } /** * @return the application templates management API */ public IApplicationTemplateMngr applicationTemplateMngr() { return this.applicationTemplateMngr; } /** * @return the targets management API */ public ITargetsMngr targetsMngr() { return this.targetsMngr; } /** * @return the debug API */ public IDebugMngr debugMngr() { return this.debugMngr; } /** * @return the commands API */ public ICommandsMngr commandsMngr() { return this.commandsMngr; } /** * @return the preferences API */ public IPreferencesMngr preferencesMngr() { return this.preferencesMngr; } /** * @return the autonomic API */ public IAutonomicMngr autonomicMngr() { return this.autonomicMngr; } // Convenience methods for non-OSGi environments /** * Adds a messaging client factory. * <p> * WARNING: this method is made available only to be used in non-OSGi environments * (e.g. Maven, embedded mode, etc). If you are not sure, do not use it. * </p> * * @param clientFactory a non-null client factory */ public void addMessagingFactory( IMessagingClientFactory clientFactory ) { if( this.messagingClient.getRegistry() == null ) { MessagingClientFactoryRegistry registry = new MessagingClientFactoryRegistry(); this.messagingClient.setRegistry( registry ); } this.messagingClient.getRegistry().addMessagingClientFactory( clientFactory ); } /** * Removes a messaging client factory. * <p> * WARNING: this method is made available only to be used in non-OSGi environments * (e.g. Maven, embedded mode, etc). If you are not sure, do not use it. * </p> * * @param clientFactory a non-null client factory */ public void removeMessagingFactory( IMessagingClientFactory clientFactory ) { if( this.messagingClient.getRegistry() != null ) this.messagingClient.getRegistry().removeMessagingClientFactory( clientFactory ); } // MBean methods @Override public int getApplicationCount() { return this.applicationMngr.getManagedApplications().size(); } @Override public int getApplicationTemplateCount() { return this.applicationTemplateMngr.getApplicationTemplates().size(); } @Override public int getInstancesCount() { int result = 0; for( ManagedApplication ma : this.applicationMngr.getManagedApplications()) result += InstanceHelpers.getAllInstances( ma.getApplication()).size(); return result; } @Override public int getScopedInstancesCount() { int result = 0; for( ManagedApplication ma : this.applicationMngr.getManagedApplications()) result += InstanceHelpers.findAllScopedInstances( ma.getApplication()).size(); return result; } // Private utilities /** * Restores the states of all the instances from the current target handlers. */ void restoreAllInstances() { // instancesMngr() instead of this.instancesMngr (for unit tests). this.logger.fine( "Restoring all the instance states from the current target handlers." ); for( ManagedApplication ma : this.applicationMngr.getManagedApplications()) { // Build a new snapshot on every loop List<TargetHandler> snapshot = this.defaultTargetHandlerResolver.getTargetHandlersSnapshot(); for( TargetHandler targetHandler : snapshot ) instancesMngr().restoreInstanceStates( ma, targetHandler ); } } /** * Restores the states of all the instances from a given target handler. */ void restoreInstancesFrom( TargetHandler targetHandler ) { // instancesMngr() instead of this.instancesMngr (for unit tests). this.logger.fine( "Restoring the instance states with the '" + targetHandler.getTargetId() + "' target handler." ); for( ManagedApplication ma : this.applicationMngr.getManagedApplications()) instancesMngr().restoreInstanceStates( ma, targetHandler ); } }