/** * Copyright 2014-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.agent.internal; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.logging.Logger; import net.roboconf.agent.internal.lifecycle.AbstractLifeCycleManager; import net.roboconf.agent.internal.misc.AgentUtils; import net.roboconf.core.model.beans.ApplicationTemplate; import net.roboconf.core.model.beans.Component; import net.roboconf.core.model.beans.Import; import net.roboconf.core.model.beans.Instance; import net.roboconf.core.model.beans.Instance.InstanceStatus; import net.roboconf.core.model.helpers.ComponentHelpers; import net.roboconf.core.model.helpers.ImportHelpers; import net.roboconf.core.model.helpers.InstanceHelpers; import net.roboconf.core.model.helpers.VariableHelpers; import net.roboconf.core.utils.Utils; import net.roboconf.messaging.api.AbstractMessageProcessor; import net.roboconf.messaging.api.business.IAgentClient; import net.roboconf.messaging.api.business.ListenerCommand; import net.roboconf.messaging.api.messages.Message; import net.roboconf.messaging.api.messages.from_agent_to_agent.MsgCmdAddImport; import net.roboconf.messaging.api.messages.from_agent_to_agent.MsgCmdRemoveImport; import net.roboconf.messaging.api.messages.from_agent_to_agent.MsgCmdRequestImport; import net.roboconf.messaging.api.messages.from_agent_to_dm.MsgNotifInstanceChanged; import net.roboconf.messaging.api.messages.from_agent_to_dm.MsgNotifInstanceRemoved; import net.roboconf.messaging.api.messages.from_agent_to_dm.MsgNotifLogs; import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdAddInstance; import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdChangeBinding; import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdChangeInstanceState; import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdChangeLogLevel; import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdGatherLogs; import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdRemoveInstance; import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdResynchronize; import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdSendInstances; import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdSetScopedInstance; import net.roboconf.messaging.api.messages.from_dm_to_agent.MsgCmdUpdateProbeConfiguration; import net.roboconf.messaging.api.messages.from_dm_to_dm.MsgEcho; import net.roboconf.plugin.api.PluginException; import net.roboconf.plugin.api.PluginInterface; /** * The class (thread) in charge of processing messages received by the agent. * <p> * The key idea in this processor is that the method {@link #processMessage(Message)} * CANNOT be interrupted when it is processing a message. * </p> * <p> * The agent can indicate * "Hey! I have a new configuration, I have to replace you." * But in this case, the agent will not directly replace the processor. * Instead, it will wait the current processing to complete. And only then, it will * replace the messaging client and the processor. * </p> * * @author Vincent Zurczak - Linagora * @author Amadou Diarra - UGA */ public class AgentMessageProcessor extends AbstractMessageProcessor<IAgentClient> { private final Logger logger = Logger.getLogger( getClass().getName()); private final Agent agent; Instance scopedInstance; /** * A map used for external exports. * <p> * Key = template name, value = a set of application names. * </p> */ final Map<String,Set<String>> applicationBindings = new HashMap<> (); /** * A local cache to store external imports, even those that are NOT used by instances. * <p> * Several applications may have the same template, and thus, exports the same variable * to other applications. But only ONE application can be bound with another. From the messaging's * point of view, the agent receives messages from all these applications. It is then up to it * to filter those it can use. * </p> * <p> * The imports that are compliant with {@link #applicationBindings} are directly injected in * the instance imports. But ALL the external imports are cached in this map. * </p> */ final Map<String,Collection<Import>> applicationNameToExternalExports = new HashMap<> (); /** * Constructor. * @param agent */ public AgentMessageProcessor( Agent agent ) { super( "Roboconf Agent - Message Processor" ); this.agent = agent; } /* * @see net.roboconf.messaging.api.business.AbstractMessageProcessor * #processMessage(net.roboconf.messaging.api.messages.Message) */ @Override protected void processMessage( Message message ) { this.logger.fine( "A message of type " + message.getClass().getSimpleName() + " was received and is about to be processed." ); try { if( message instanceof MsgCmdSetScopedInstance ) processMsgSetScopedInstance((MsgCmdSetScopedInstance) message ); else if( message instanceof MsgCmdRemoveInstance ) processMsgRemoveInstance((MsgCmdRemoveInstance) message ); else if( message instanceof MsgCmdAddInstance ) processMsgAddInstance((MsgCmdAddInstance) message ); else if( message instanceof MsgCmdChangeInstanceState ) processMsgChangeInstanceState((MsgCmdChangeInstanceState) message ); else if( message instanceof MsgCmdAddImport ) processMsgAddImport((MsgCmdAddImport) message ); else if( message instanceof MsgCmdRemoveImport ) processMsgRemoveImport((MsgCmdRemoveImport) message, true ); else if( message instanceof MsgCmdRequestImport ) processMsgRequestImport((MsgCmdRequestImport) message ); else if( message instanceof MsgCmdSendInstances ) processMsgSendInstances((MsgCmdSendInstances) message ); else if( message instanceof MsgCmdResynchronize ) processMsgResynchronize((MsgCmdResynchronize) message ); else if( message instanceof MsgEcho ) processMsgEcho((MsgEcho) message ); else if( message instanceof MsgCmdChangeBinding ) processMsgChangeBinding((MsgCmdChangeBinding) message ); else if( message instanceof MsgCmdUpdateProbeConfiguration ) processUpdateProbeConfiguration((MsgCmdUpdateProbeConfiguration) message ); else if( message instanceof MsgCmdChangeLogLevel ) processChangeLogLevel((MsgCmdChangeLogLevel) message ); else if( message instanceof MsgCmdGatherLogs ) processGatherLogs((MsgCmdGatherLogs) message ); else this.logger.warning( getName() + " got an undetermined message to process. " + message.getClass().getName()); } catch( IOException e ) { this.logger.severe( "A problem occurred with the messaging. " + e.getMessage()); Utils.logException( this.logger, e ); } catch( PluginException e ) { this.logger.severe( "A problem occurred with a plug-in. " + e.getMessage()); Utils.logException( this.logger, e ); } } /** * Gathers the main log files and sends them to the DM. * @param message the incoming message * @throws IOException if something went wrong */ private void processGatherLogs( MsgCmdGatherLogs message ) throws IOException { Map<String,byte[]> logFiles = AgentUtils.collectLogs( this.agent.karafData ); MsgNotifLogs msg = new MsgNotifLogs( this.agent.getApplicationName(), this.agent.getScopedInstancePath(), logFiles ); this.messagingClient.sendMessageToTheDm( msg ); } /** * Changes the log level. * @param message the incoming message * @throws IOException if something went wrong */ private void processChangeLogLevel( MsgCmdChangeLogLevel message ) throws IOException { AgentUtils.changeRoboconfLogLevel( message.getLogLevel(), this.agent.karafEtc ); } /** * Updates the probe configuration of a given instance. * @param message the incoming message * @throws IOException if something went wrong */ private void processUpdateProbeConfiguration( MsgCmdUpdateProbeConfiguration message ) throws IOException { Instance inst = InstanceHelpers.findInstanceByPath( this.scopedInstance, message.getInstancePath()); if( inst == null ) this.logger.warning( "Instance " + message.getInstancePath() + " could not be found. Probe configuration will not be updated." ); else AgentUtils.copyInstanceResources( inst, message.getProbeResources()); } /** * Updates the application bindings. * <p> * These bindings can be modified when the application is running. In this * case, it triggers a reconfiguration when necessary. * </p> * * @param msg an change binding message */ void processMsgChangeBinding( MsgCmdChangeBinding msg ) throws IOException { this.logger.fine( "Updating bound applications for prefix " + msg.getExternalExportsPrefix() + "." ); // We need to get the delta between the new bindings and the previous ones Set<String> oldBindings = this.applicationBindings.get( msg.getExternalExportsPrefix()); if( oldBindings == null ) oldBindings = new HashSet<>( 0 ); Set<String> newBindings = msg.getAppNames(); if( newBindings == null ) newBindings = new HashSet<>( 0 ); // Removed names are those present before but not after Set<String> removedAppNames = new LinkedHashSet<>( oldBindings ); removedAppNames.removeAll( newBindings ); // Added ones are those present after but not before Set<String> addedAppNames = new LinkedHashSet<>( newBindings ); addedAppNames.removeAll( oldBindings ); // If so, act as if the associated imports had been removed for( String removedAppName : removedAppNames ) { this.logger.fine( "Unbiding prefix " + msg.getExternalExportsPrefix() + " from application " + removedAppName + "." ); // We need to remove all the instance paths associated with this inter-app prefix. // For that, we act as if we had received MsgCmdRemoveImport messages. // It means we will indirectly update some instances (at least once). And that we may invoke // update scripts more than once. This is not very efficient, but it is to preserve the plug-in API // and the way we pass parameters to our scripts. // Indeed, when we remove an import, we pass it to our plug-ins. The scripts can handle them. Handling // a collection (e.g. a list) would make scripts and recipes more complicated. Besides, it would not be // symmetrical with added imports. List<String> instancePaths = new ArrayList<> (); for( Instance instance : InstanceHelpers.buildHierarchicalList( this.scopedInstance )) { Collection<Import> imports = instance.getImports().get( msg.getExternalExportsPrefix()); if( imports == null ) continue; for( Import imp : imports ) instancePaths.add( imp.getInstancePath()); } // Now that we have the instance paths to remove, // remove the associated imports. for( String path : instancePaths ) { MsgCmdRemoveImport fakeMsg = new MsgCmdRemoveImport( removedAppName, msg.getExternalExportsPrefix(), path ); try { processMsgRemoveImport( fakeMsg, false ); } catch( PluginException e ) { this.logger.severe( "A problem occurred with a plug-in. " + e.getMessage()); Utils.logException( this.logger, e ); } } } // Update the bindings this.applicationBindings.put( msg.getExternalExportsPrefix(), msg.getAppNames()); // Now, we need to find all the external imports associated with the new application. // Then, we will act as if we had received AddImport messages. This is symmetrical with what we // did with removed imports. // This strategy with a local cache prevents use from requesting exports from other agents. for( String addedAppName : addedAppNames ) { this.logger.fine( "Binding prefix " + msg.getExternalExportsPrefix() + " with application " + addedAppName + "." ); Collection<Import> imports = this.applicationNameToExternalExports.get( addedAppName ); if( imports == null ) continue; for( Import imp : imports ) { MsgCmdAddImport fakeMsg = new MsgCmdAddImport( addedAppName, imp.getComponentName(), imp.getInstancePath(), imp.getExportedVars()); try { processMsgAddImport( fakeMsg ); } catch( PluginException e ) { this.logger.severe( "A problem occurred with a plug-in. " + e.getMessage()); Utils.logException( this.logger, e ); } } } } /** * Responds to an Echo 'PING' message sent by the DM. * @param message the Echo 'PING' message sent by the DM. */ void processMsgEcho( MsgEcho message ) throws IOException { final String content = message.getContent(); MsgEcho response = new MsgEcho( content.replaceFirst( "^PING:", "PONG:" ), message.getUuid()); this.logger.fine( "Responding to DM Echo message " + content + " with response " + response.getContent()); this.messagingClient.sendMessageToTheDm( response ); } /** * Republishes all the variables managed by this agent. * @param message the initial request * @throws IOException if an error occurred with the messaging */ void processMsgResynchronize( MsgCmdResynchronize message ) throws IOException { if( this.scopedInstance != null ) { for( Instance i : InstanceHelpers.buildHierarchicalList( this.scopedInstance )) { if( i.getStatus() == InstanceStatus.DEPLOYED_STARTED ) this.messagingClient.publishExports( i ); } } } /** * Sends the local states to the DM. * @param message the initial request * @throws IOException if an error occurred with the messaging */ void processMsgSendInstances( MsgCmdSendInstances message ) throws IOException { String appName = this.agent.getApplicationName(); if( this.scopedInstance != null ) { for( Instance i : InstanceHelpers.buildHierarchicalList( this.scopedInstance )) this.messagingClient.sendMessageToTheDm( new MsgNotifInstanceChanged( appName, i )); } } /** * Sets or updates the local model. * <p> * This method is used to initialize the local model * or to update it when new instances were created. * </p> * <p> * Deletion is handled separately. * </p> * * @param msg the message to process * @throws IOException if an error occurred with the messaging * @throws PluginException if an error occurred while initializing the plug-in */ void processMsgSetScopedInstance( MsgCmdSetScopedInstance msg ) throws IOException, PluginException { Instance newScopedInstance = msg.getScopedInstance(); List<Instance> instancesToProcess = new ArrayList<> (); // Update the model and determine what must be updated if( ! InstanceHelpers.isTarget( newScopedInstance )) { this.logger.severe( "The received instance is not a scoped one. Request to update the local model is dropped." ); } else if( this.scopedInstance == null ) { this.logger.fine( "Setting the scoped instance." ); this.scopedInstance = newScopedInstance; InstanceHelpers.removeOffScopeInstances( newScopedInstance ); this.agent.setScopedInstance( newScopedInstance ); instancesToProcess.addAll( InstanceHelpers.buildHierarchicalList( this.scopedInstance )); // Propagate the external mapping into the messaging this.messagingClient.setExternalMapping( msg.getExternalExports()); // Initialize the application bindings this.applicationBindings.putAll( msg.getApplicationBindings()); // Executes the script AgentUtils.copyInstanceResources( this.scopedInstance, msg.getscriptResources()); AgentUtils.executeScriptResources( InstanceHelpers.findInstanceDirectoryOnAgent( this.scopedInstance )); // Notify the DM if( this.scopedInstance.getStatus() != InstanceStatus.DEPLOYED_STARTED ) { this.scopedInstance.setStatus( InstanceStatus.DEPLOYED_STARTED ); this.messagingClient.sendMessageToTheDm( new MsgNotifInstanceChanged( this.agent.getApplicationName(), this.scopedInstance )); } // Listen to requests from other agents for the scoped instance ONLY. // See #301. It won't be done anywhere else for the scoped instance. this.messagingClient.listenToRequestsFromOtherAgents( ListenerCommand.START, this.scopedInstance ); } // Configure the messaging. for( Instance instanceToProcess : instancesToProcess ) { this.messagingClient.listenToExportsFromOtherAgents( ListenerCommand.START, instanceToProcess ); this.messagingClient.requestExportsFromOtherAgents( instanceToProcess ); } } /** * Removes an instance to the local model. * @param msg the message to process * @throws IOException if an error occurred with the messaging */ void processMsgRemoveInstance( MsgCmdRemoveInstance msg ) throws IOException { // Remove the instance boolean removed = false; Instance instance = InstanceHelpers.findInstanceByPath( this.scopedInstance, msg.getInstancePath()); if( instance == null ) { this.logger.severe( "No instance matched " + msg.getInstancePath() + " on the agent. Request to remove it from the model is dropped." ); } else if( instance.getStatus() != InstanceStatus.NOT_DEPLOYED ) { this.logger.severe( "Instance " + msg.getInstancePath() + " cannot be removed. Instance status: " + instance.getStatus() + "." ); // We do not have to check children's status. // We cannot have a parent in NOT_DEPLOYED and a child in STARTED (as an example). } else if( instance.getParent() != null ) { removed = true; instance.getParent().getChildren().remove( instance ); this.logger.fine( "Child instance " + msg.getInstancePath() + " was removed from the model." ); } else { this.logger.fine( "The root instance " + msg.getInstancePath() + " cannot be removed. The agent must be reboot and/or reconfigured." ); } // Configure the messaging if( removed ) { this.messagingClient.sendMessageToTheDm( new MsgNotifInstanceRemoved( this.agent.getApplicationName(), instance )); for( Instance instanceToProcess : InstanceHelpers.buildHierarchicalList( instance )) this.messagingClient.listenToExportsFromOtherAgents( ListenerCommand.STOP, instanceToProcess ); } } /** * Adds an instance to the local model. * @param msg the message to process * @throws IOException if an error occurred with the messaging * @throws PluginException if an error occurred while initializing the plug-in */ void processMsgAddInstance( MsgCmdAddInstance msg ) throws IOException, PluginException { Component instanceComponent; Instance parentInstance = InstanceHelpers.findInstanceByPath( this.scopedInstance, msg.getParentInstancePath()); if( parentInstance == null ) { this.logger.severe( "The parent instance for " + msg.getParentInstancePath() + " was not found. The request to add a new instance is dropped." ); } else if(( instanceComponent = ComponentHelpers.findComponentFrom( parentInstance.getComponent(), msg.getComponentName())) == null ) { this.logger.severe( "The component " + msg.getComponentName() + " was not found in the local graph." ); } else { Instance newInstance = new Instance( msg.getInstanceName()).component( instanceComponent ); newInstance.channels.addAll( msg.getChannels()); if( msg.getData() != null ) newInstance.data.putAll( msg.getData()); if( msg.getOverridenExports() != null ) newInstance.overriddenExports.putAll( msg.getOverridenExports()); ApplicationTemplate tempApp = new ApplicationTemplate( "temp app" ); tempApp.getRootInstances().add( parentInstance ); if( ! InstanceHelpers.tryToInsertChildInstance( tempApp, parentInstance, newInstance )) { this.logger.severe( "The new '" + msg.getInstanceName() + "' instance could not be inserted into the local model." ); } else { this.messagingClient.listenToExportsFromOtherAgents( ListenerCommand.START, newInstance ); this.messagingClient.requestExportsFromOtherAgents( newInstance ); } } } /** * Deploys an instance. * @param msg the message to process * @throws IOException if an error occurred with the messaging or while manipulating the file system * @throws PluginException if something went wrong with the plug-in */ void processMsgChangeInstanceState( MsgCmdChangeInstanceState msg ) throws IOException, PluginException { PluginInterface plugin; Instance instance = InstanceHelpers.findInstanceByPath( this.scopedInstance, msg.getInstancePath()); if( instance == null ) this.logger.severe( "No instance matched " + msg.getInstancePath() + " on the agent. Request to deploy it is dropped." ); else if( instance.getParent() == null ) this.logger.severe( "No action on the root instance is permitted." ); else if(( plugin = this.agent.findPlugin( instance )) == null ) this.logger.severe( "No plug-in was found to deploy " + msg.getInstancePath() + "." ); else AbstractLifeCycleManager .build( instance, this.agent.getApplicationName(), this.messagingClient) .changeInstanceState( instance, plugin, msg.getNewState(), msg.getFileNameToFileContent()); } /** * Publishes its exports when required. * @param msg the message process * @throws IOException if an error occurred with the messaging */ void processMsgRequestImport( MsgCmdRequestImport msg ) throws IOException { for( Instance instance : InstanceHelpers.buildHierarchicalList( this.scopedInstance )) { if( instance.getStatus() == InstanceStatus.DEPLOYED_STARTED ) this.messagingClient.publishExports( instance, msg.getComponentOrFacetName()); } } /** * Removes (if necessary) an import from the model instances. * @param msg the message process * @param realMessage true if is real received message, false for a fake one * @throws IOException if an error occurred with the messaging * @throws PluginException if an error occurred with a plug-in */ void processMsgRemoveImport( MsgCmdRemoveImport msg, boolean realMessage ) throws IOException, PluginException { // Track ALL external exports - only for real messages String appName = this.agent.getApplicationName(); if( realMessage && ! msg.getApplicationOrContextName().equals( appName )) { removeCachedExternalImport( msg ); } // Go through all the instances to see which ones are impacted. // If it is an external exports that is removed, it will not be found in this instance. for( Instance instance : InstanceHelpers.buildHierarchicalList( this.scopedInstance )) { Set<String> importPrefixes = VariableHelpers.findPrefixesForImportedVariables( instance ); if( ! importPrefixes.contains( msg.getComponentOrFacetName())) continue; // Is there an import to remove? Collection<Import> imports = instance.getImports().get( msg.getComponentOrFacetName()); Import toRemove = ImportHelpers.findImportByExportingInstance( imports, msg.getRemovedInstancePath()); if( toRemove == null ) continue; // Remove the import and publish an update to the DM imports.remove( toRemove ); if( imports.isEmpty()) instance.getImports().remove( msg.getComponentOrFacetName()); this.logger.fine( "Removing import from " + InstanceHelpers.computeInstancePath( instance ) + ". Removed exporting instance: " + msg.getRemovedInstancePath()); this.messagingClient.sendMessageToTheDm( new MsgNotifInstanceChanged( appName, instance )); // Update the life cycle if necessary PluginInterface plugin = this.agent.findPlugin( instance ); if( plugin == null ) throw new PluginException( "No plugin was found for " + InstanceHelpers.computeInstancePath( instance )); AbstractLifeCycleManager .build( instance, this.agent.getApplicationName(), this.messagingClient) .updateStateFromImports( instance, plugin, toRemove, InstanceStatus.DEPLOYED_STOPPED ); } // Import changed => check all the waiting for ancestors... startChildrenInstancesWaitingForAncestors(); } /** * Receives and adds (if necessary) a new import to the model instances. * @param msg the message process * @throws IOException if an error occurred with the messaging * @throws PluginException if an error occurred with a plug-in */ void processMsgAddImport( MsgCmdAddImport msg ) throws IOException, PluginException { // We must filter the new import. // It must either come from THIS application, or be referenced in the // application bindings. Otherwise, drop the update. if( ! msg.getApplicationOrContextName().equals( this.agent.getApplicationName())) { // Track ALL external exports Collection<Import> imports = this.applicationNameToExternalExports.get( msg.getApplicationOrContextName()); if( imports == null ) { imports = new LinkedHashSet<> (); this.applicationNameToExternalExports.put( msg.getApplicationOrContextName(), imports ); } // We use a set to prevent duplicates, so no need to distinguish fake and real messages imports.add( new Import( msg.getAddedInstancePath(), msg.getComponentOrFacetName(), msg.getExportedVariables())); // Should we go further? // If it is not in the application binding, this import should not be added in the instance imports. Set<String> appNames = this.applicationBindings.get( msg.getComponentOrFacetName()); if( appNames == null || ! appNames.contains( msg.getApplicationOrContextName())) { this.logger.fine( "An external export was received (" + msg.getComponentOrFacetName() + ") but did not match any of the bound applications." ); return; } } // Go through all the instances to see which ones need an update String appName = this.agent.getApplicationName(); for( Instance instance : InstanceHelpers.buildHierarchicalList( this.scopedInstance )) { // This instance does not depends on it Set<String> importPrefixes = VariableHelpers.findPrefixesForImportedVariables( instance ); if( ! importPrefixes.contains( msg.getComponentOrFacetName())) continue; // If an instance depends on its component, make sure it does not add itself to the imports. // Example: MongoDB may depend on other MongoDB instances. if( Objects.equals( InstanceHelpers.computeInstancePath( instance ), msg.getAddedInstancePath())) continue; // Create the right import Import imp = ImportHelpers.buildTailoredImport( instance, msg.getAddedInstancePath(), msg.getComponentOrFacetName(), msg.getExportedVariables()); // Add the import and publish an update to the DM this.logger.fine( "Adding import to " + InstanceHelpers.computeInstancePath( instance ) + ". New import: " + imp ); ImportHelpers.addImport( instance, msg.getComponentOrFacetName(), imp ); this.messagingClient.sendMessageToTheDm( new MsgNotifInstanceChanged( appName, instance )); // Update the life cycle if necessary PluginInterface plugin = this.agent.findPlugin( instance ); if( plugin == null ) throw new PluginException( "No plugin was found for " + InstanceHelpers.computeInstancePath( instance )); AbstractLifeCycleManager .build( instance, this.agent.getApplicationName(), this.messagingClient) .updateStateFromImports( instance, plugin, imp, InstanceStatus.DEPLOYED_STARTED ); } // Import changed => check all the waiting for ancestors... startChildrenInstancesWaitingForAncestors(); } private void removeCachedExternalImport( MsgCmdRemoveImport msg ) { Collection<Import> imports = this.applicationNameToExternalExports.get( msg.getApplicationOrContextName()); if( imports != null ) { Import toRemove = null; for( Iterator<Import> it = imports.iterator(); it.hasNext() && toRemove == null; ) { Import cur = it.next(); if( cur.getInstancePath().equals( msg.getRemovedInstancePath())) toRemove = cur; } if( toRemove != null ) imports.remove( toRemove ); if( imports.isEmpty()) this.applicationNameToExternalExports.remove( msg.getApplicationOrContextName()); } } /** * Starts children instances when they are waiting for their ancestors to start. * <p> * To invoke every time imports change. * </p> * @throws IOException if something went wrong * @throws PluginException if something went wrong */ private void startChildrenInstancesWaitingForAncestors() throws IOException, PluginException { List<Instance> childrenInstances = InstanceHelpers.buildHierarchicalList( this.scopedInstance ); childrenInstances.remove( this.scopedInstance ); for( Instance childInstance : childrenInstances ) { if( childInstance.getStatus() != InstanceStatus.WAITING_FOR_ANCESTOR ) continue; if( childInstance.getParent().getStatus() != InstanceStatus.DEPLOYED_STARTED ) continue; PluginInterface plugin = this.agent.findPlugin( childInstance ); if( plugin == null ) this.logger.severe( "No plug-in was found for " + InstanceHelpers.computeInstancePath( childInstance ) + "." ); else AbstractLifeCycleManager .build( childInstance, this.agent.getApplicationName(), this.messagingClient) .changeInstanceState( childInstance, plugin, InstanceStatus.DEPLOYED_STARTED, null ); } } }