@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);
  }