@Override
  public void redeployAll() {
    Map<String, String> wars = MutableMap.copyOf(getConfig(WARS_BY_CONTEXT));
    String redeployPrefix = "Redeploy all WARs (count " + wars.size() + ")";

    log.debug("Redeplying all WARs across cluster " + this + ": " + getConfig(WARS_BY_CONTEXT));

    Iterable<CanDeployAndUndeploy> targetEntities =
        Iterables.filter(getChildren(), CanDeployAndUndeploy.class);
    TaskBuilder<Void> tb =
        Tasks.<Void>builder()
            .parallel(true)
            .name(redeployPrefix + " across cluster (size " + Iterables.size(targetEntities) + ")");
    for (Entity targetEntity : targetEntities) {
      TaskBuilder<Void> redeployAllToTarget =
          Tasks.<Void>builder()
              .name(redeployPrefix + " at " + targetEntity + " (after ready check)");
      for (String warContextPath : wars.keySet()) {
        redeployAllToTarget.add(
            Effectors.invocation(
                targetEntity,
                DEPLOY,
                MutableMap.of("url", wars.get(warContextPath), "targetName", warContextPath)));
      }
      tb.add(
          whenServiceUp(
              targetEntity,
              redeployAllToTarget.build(),
              redeployPrefix + " at " + targetEntity + " when ready"));
    }
    DynamicTasks.queueIfPossible(tb.build()).orSubmitAsync(this).asTask().getUnchecked();
  }
  @Override
  public void undeploy(String targetName) {
    checkNotNull(targetName, "targetName");
    targetName = FILENAME_TO_WEB_CONTEXT_MAPPER.convertDeploymentTargetNameToContext(targetName);

    // set it up so future nodes get the right wars
    if (!removeFromWarsByContext(this, targetName)) {
      DynamicTasks.submit(
          Tasks.warning(
              "Context "
                  + targetName
                  + " not known at "
                  + this
                  + "; attempting to undeploy regardless",
              null),
          this);
    }

    log.debug(
        "Undeploying "
            + targetName
            + " across cluster "
            + this
            + "; WARs now "
            + getConfig(WARS_BY_CONTEXT));

    Iterable<CanDeployAndUndeploy> targets =
        Iterables.filter(getChildren(), CanDeployAndUndeploy.class);
    TaskBuilder<Void> tb =
        Tasks.<Void>builder()
            .parallel(true)
            .name(
                "Undeploy "
                    + targetName
                    + " across cluster (size "
                    + Iterables.size(targets)
                    + ")");
    for (Entity target : targets) {
      tb.add(
          whenServiceUp(
              target,
              Effectors.invocation(target, UNDEPLOY, MutableMap.of("targetName", targetName)),
              "Undeploy " + targetName + " at " + target + " when ready"));
    }
    DynamicTasks.queueIfPossible(tb.build()).orSubmitAsync(this).asTask().getUnchecked();

    // Update attribute
    Set<String> deployedWars = MutableSet.copyOf(getAttribute(DEPLOYED_WARS));
    deployedWars.remove(
        FILENAME_TO_WEB_CONTEXT_MAPPER.convertDeploymentTargetNameToContext(targetName));
    setAttribute(DEPLOYED_WARS, deployedWars);
  }
 @Override
 protected <T> Task<T> runAtEntity(
     final Entity entity,
     final Effector<T> eff,
     @SuppressWarnings("rawtypes") final Map parameters) {
   manageIfNecessary(entity, eff);
   // prefer to submit this from the current execution context so it sets up correct cross-context
   // chaining
   ExecutionContext ec = BasicExecutionContext.getCurrentExecutionContext();
   if (ec == null) {
     log.debug("Top-level effector invocation: {} on {}", eff, entity);
     ec = getExecutionContext(entity);
   }
   return ec.submit(Effectors.invocation(entity, eff, parameters));
 }
  @Override
  public void deploy(String url, String targetName) {
    checkNotNull(url, "url");
    checkNotNull(targetName, "targetName");
    targetName = FILENAME_TO_WEB_CONTEXT_MAPPER.convertDeploymentTargetNameToContext(targetName);

    // set it up so future nodes get the right wars
    addToWarsByContext(this, url, targetName);

    log.debug(
        "Deploying "
            + targetName
            + "->"
            + url
            + " across cluster "
            + this
            + "; WARs now "
            + getConfig(WARS_BY_CONTEXT));

    Iterable<CanDeployAndUndeploy> targets =
        Iterables.filter(getChildren(), CanDeployAndUndeploy.class);
    TaskBuilder<Void> tb =
        Tasks.<Void>builder()
            .parallel(true)
            .name("Deploy " + targetName + " to cluster (size " + Iterables.size(targets) + ")");
    for (Entity target : targets) {
      tb.add(
          whenServiceUp(
              target,
              Effectors.invocation(
                  target, DEPLOY, MutableMap.of("url", url, "targetName", targetName)),
              "Deploy " + targetName + " to " + target + " when ready"));
    }
    DynamicTasks.queueIfPossible(tb.build()).orSubmitAsync(this).asTask().getUnchecked();

    // Update attribute
    // TODO support for atomic sensor update (should be part of standard tooling; NB there is some
    // work towards this, according to @aledsage)
    Set<String> deployedWars = MutableSet.copyOf(getAttribute(DEPLOYED_WARS));
    deployedWars.add(targetName);
    setAttribute(DEPLOYED_WARS, deployedWars);
  }