/**
   * Deploys an instance (prerequisite: NOT_DEPLOYED).
   *
   * @param instance the instance
   * @param plugin the associated plug-in
   * @param fileNameToFileContent a map containing resources for the plug-in
   * @throws IOException if something went wrong
   */
  void deploy(Instance instance, PluginInterface plugin, Map<String, byte[]> fileNameToFileContent)
      throws IOException {

    String instancePath = InstanceHelpers.computeInstancePath(instance);
    if (instance.getStatus() != InstanceStatus.NOT_DEPLOYED) {
      this.logger.fine(
          instancePath
              + " cannot be deployed. Prerequisite status: NOT_DEPLOYED (but was "
              + instance.getStatus()
              + ").");

    } else if (instance.getParent() != null
        && instance.getParent().getStatus() != InstanceStatus.DEPLOYED_STARTED
        && instance.getParent().getStatus() != InstanceStatus.DEPLOYED_STOPPED
        && instance.getParent().getStatus() != InstanceStatus.UNRESOLVED
        && instance.getParent().getStatus() != InstanceStatus.WAITING_FOR_ANCESTOR) {
      this.logger.fine(
          instancePath
              + " cannot be deployed because its parent is not deployed. Parent status: "
              + instance.getParent().getStatus()
              + ".");

    } else {
      this.logger.fine("Deploying instance " + instancePath);

      // User reporting => deploying...
      instance.setStatus(InstanceStatus.DEPLOYING);
      try {
        this.messagingClient.sendMessageToTheDm(
            new MsgNotifInstanceChanged(this.appName, instance));

        // Clean up the potential remains of a previous installation
        AgentUtils.deleteInstanceResources(instance);

        // Copy the resources
        AgentUtils.copyInstanceResources(instance, fileNameToFileContent);

        // Initialize the plugin
        plugin.initialize(instance);

        // Invoke the plug-in
        plugin.deploy(instance);
        instance.setStatus(InstanceStatus.DEPLOYED_STOPPED);
        this.messagingClient.sendMessageToTheDm(
            new MsgNotifInstanceChanged(this.appName, instance));

      } catch (Exception e) {
        this.logger.severe("An error occured while deploying " + instancePath);
        Utils.logException(this.logger, e);

        instance.setStatus(InstanceStatus.NOT_DEPLOYED);
        this.messagingClient.sendMessageToTheDm(
            new MsgNotifInstanceChanged(this.appName, instance));
      }
    }
  }
  /**
   * Undeploys an instance (prerequisite: DEPLOYED_STOPPED).
   *
   * @param instance the instance
   * @param plugin the associated plug-in
   * @throws IOException if something went wrong
   */
  void undeploy(Instance instance, PluginInterface plugin) throws IOException {

    // Preliminary check
    String instancePath = InstanceHelpers.computeInstancePath(instance);
    if (instance.getStatus() != InstanceStatus.DEPLOYED_STOPPED
        && instance.getStatus() != InstanceStatus.UNRESOLVED
        && instance.getStatus() != InstanceStatus.WAITING_FOR_ANCESTOR) {
      this.logger.fine(
          instancePath
              + " cannot be undeployed. Prerequisite status: DEPLOYED_STOPPED or UNRESOLVED or WAITING_FOR_ANCESTOR (but was "
              + instance.getStatus()
              + ").");
      return;
    }

    // Children may have to be marked as stopped.
    // From a plug-in point of view, we only use the one for the given instance.
    // Children are SUPPOSED to be stopped immediately.
    List<Instance> instancesToUndeploy = InstanceHelpers.buildHierarchicalList(instance);
    Collections.reverse(instancesToUndeploy);

    InstanceStatus newStatus = InstanceStatus.NOT_DEPLOYED;
    try {
      // Update the statuses if necessary
      for (Instance i : instancesToUndeploy) {
        if (i.getStatus() == InstanceStatus.NOT_DEPLOYED) continue;

        i.setStatus(InstanceStatus.UNDEPLOYING);
        this.messagingClient.sendMessageToTheDm(new MsgNotifInstanceChanged(this.appName, i));
        this.messagingClient.unpublishExports(i);
      }

      // Hypothesis: undeploying an instance undeploys its children too.
      try {
        plugin.undeploy(instance);

        // Delete files for undeployed instances
        for (Instance i : instancesToUndeploy) AgentUtils.deleteInstanceResources(i);

      } catch (PluginException e) {
        this.logger.severe(
            "An error occured while undeploying " + InstanceHelpers.computeInstancePath(instance));
        Utils.logException(this.logger, e);
        newStatus = InstanceStatus.DEPLOYED_STOPPED;
      }

    } catch (Exception e) {
      newStatus = InstanceStatus.DEPLOYED_STOPPED;
      Utils.logException(this.logger, e);

    } finally {
      // Update the status of all the instances
      for (Instance i : instancesToUndeploy) {
        i.setStatus(newStatus);
        this.messagingClient.sendMessageToTheDm(new MsgNotifInstanceChanged(this.appName, i));
      }
    }
  }