@SuppressWarnings("deprecation")
  protected void afterEnd(Map flags, Task<?> task) {
    activeTaskCount.decrementAndGet();
    incompleteTaskCount.decrementAndGet();

    if (log.isTraceEnabled()) log.trace(this + " afterEnd, task: " + task);
    ExecutionUtils.invoke(flags.get("newTaskEndCallback"), task);
    List l = (List) flags.get("tagLinkedPreprocessors");
    Collections.reverse(l);
    for (Object li : l) {
      TaskPreprocessor t = (TaskPreprocessor) li;
      t.onEnd(flags, task);
    }

    PerThreadCurrentTaskHolder.perThreadCurrentTask.remove();
    ((BasicTask) task).endTimeUtc = System.currentTimeMillis();
    // clear thread _after_ endTime set, so we won't get a null thread when there is no end-time
    if (RENAME_THREADS) {
      String newThreadName = "brooklyn-" + LanguageUtils.newUid();
      ((BasicTask) task).thread.setName(newThreadName);
    }
    ((BasicTask) task).thread = null;
    synchronized (task) {
      task.notifyAll();
    }

    ExpirationPolicy expirationPolicy = (ExpirationPolicy) flags.get("expirationPolicy");
    if (expirationPolicy == null) expirationPolicy = ExpirationPolicy.IMMEDIATE;
    if (expirationPolicy == ExpirationPolicy.IMMEDIATE) {
      for (Object t : ((BasicTask) task).tags) {
        getMutableTasksWithTag(t).remove(task);
      }
    }
  }
  @SuppressWarnings("deprecation")
  protected void beforeStart(Map flags, Task<?> task) {
    activeTaskCount.incrementAndGet();

    // set thread _before_ start time, so we won't get a null thread when there is a start-time
    if (log.isTraceEnabled()) log.trace("" + this + " beforeStart, task: " + task);
    if (!task.isCancelled()) {
      ((BasicTask) task).thread = Thread.currentThread();
      if (RENAME_THREADS) {
        String newThreadName =
            "brooklyn-"
                + CaseFormat.LOWER_HYPHEN.to(
                    CaseFormat.LOWER_CAMEL, task.getDisplayName().replace(" ", ""))
                + "-"
                + task.getId().substring(0, 8);
        ((BasicTask) task).thread.setName(newThreadName);
      }
      PerThreadCurrentTaskHolder.perThreadCurrentTask.set(task);
      ((BasicTask) task).startTimeUtc = System.currentTimeMillis();
    }
    for (Object to : (Collection) flags.get("tagLinkedPreprocessors")) {
      TaskPreprocessor t = (TaskPreprocessor) to;
      t.onStart(flags, task);
    }
    ExecutionUtils.invoke(flags.get("newTaskStartCallback"), task);
  }
  protected void afterEnd(Map<?, ?> flags, Task<?> task) {
    activeTaskCount.decrementAndGet();
    incompleteTaskCount.decrementAndGet();

    if (log.isTraceEnabled()) log.trace(this + " afterEnd, task: " + task);
    ExecutionUtils.invoke(flags.get("newTaskEndCallback"), task);

    PerThreadCurrentTaskHolder.perThreadCurrentTask.remove();
    ((TaskInternal<?>) task).setEndTimeUtc(System.currentTimeMillis());
    // clear thread _after_ endTime set, so we won't get a null thread when there is no end-time
    if (RENAME_THREADS) {
      String newThreadName = "brooklyn-" + Identifiers.makeRandomId(8);
      task.getThread().setName(newThreadName);
    }
    ((TaskInternal<?>) task).setThread(null);
    synchronized (task) {
      task.notifyAll();
    }

    for (ExecutionListener listener : listeners) {
      try {
        listener.onTaskDone(task);
      } catch (Exception e) {
        log.warn("Error notifying listener " + listener + " of task " + task + " done", e);
      }
    }
  }
  protected void beforeStart(Map<?, ?> flags, Task<?> task) {
    activeTaskCount.incrementAndGet();

    // set thread _before_ start time, so we won't get a null thread when there is a start-time
    if (log.isTraceEnabled()) log.trace("" + this + " beforeStart, task: " + task);
    if (!task.isCancelled()) {
      ((TaskInternal<?>) task).setThread(Thread.currentThread());
      if (RENAME_THREADS) {
        String newThreadName =
            "brooklyn-"
                + CaseFormat.LOWER_HYPHEN.to(
                    CaseFormat.LOWER_CAMEL, task.getDisplayName().replace(" ", ""))
                + "-"
                + task.getId().substring(0, 8);
        task.getThread().setName(newThreadName);
      }
      PerThreadCurrentTaskHolder.perThreadCurrentTask.set(task);
      ((TaskInternal<?>) task).setStartTimeUtc(System.currentTimeMillis());
    }
    ExecutionUtils.invoke(flags.get("newTaskStartCallback"), task);
  }
  @SuppressWarnings("rawtypes")
  public void afterPropertiesSet() throws Exception {
    final Configuration cfg = ConfigurationUtils.createFrom(configuration, properties);

    buildGenericOptions(cfg);

    if (StringUtils.hasText(user)) {
      UserGroupInformation ugi =
          UserGroupInformation.createProxyUser(user, UserGroupInformation.getLoginUser());
      ugi.doAs(
          new PrivilegedExceptionAction<Void>() {

            @Override
            public Void run() throws Exception {
              job = new Job(cfg);
              return null;
            }
          });
    } else {
      job = new Job(cfg);
    }

    ClassLoader loader =
        (beanClassLoader != null
            ? beanClassLoader
            : org.springframework.util.ClassUtils.getDefaultClassLoader());

    if (jar != null) {
      JobConf conf = (JobConf) job.getConfiguration();
      conf.setJar(jar.getURI().toString());
      loader = ExecutionUtils.createParentLastClassLoader(jar, beanClassLoader, cfg);
      conf.setClassLoader(loader);
    }

    // set first to enable auto-detection of K/V to skip the key/value types to be specified
    if (mapper != null) {
      Class<? extends Mapper> mapperClass = resolveClass(mapper, loader, Mapper.class);
      job.setMapperClass(mapperClass);
      configureMapperTypesIfPossible(job, mapperClass);
    }

    if (reducer != null) {
      Class<? extends Reducer> reducerClass = resolveClass(reducer, loader, Reducer.class);
      job.setReducerClass(reducerClass);
      configureReducerTypesIfPossible(job, reducerClass);
    }

    if (StringUtils.hasText(name)) {
      job.setJobName(name);
    }
    if (combiner != null) {
      job.setCombinerClass(resolveClass(combiner, loader, Reducer.class));
    }
    if (groupingComparator != null) {
      job.setGroupingComparatorClass(resolveClass(groupingComparator, loader, RawComparator.class));
    }
    if (inputFormat != null) {
      job.setInputFormatClass(resolveClass(inputFormat, loader, InputFormat.class));
    }
    if (mapKey != null) {
      job.setMapOutputKeyClass(resolveClass(mapKey, loader, Object.class));
    }
    if (mapValue != null) {
      job.setMapOutputValueClass(resolveClass(mapValue, loader, Object.class));
    }
    if (numReduceTasks != null) {
      job.setNumReduceTasks(numReduceTasks);
    }
    if (key != null) {
      job.setOutputKeyClass(resolveClass(key, loader, Object.class));
    }
    if (value != null) {
      job.setOutputValueClass(resolveClass(value, loader, Object.class));
    }
    if (outputFormat != null) {
      job.setOutputFormatClass(resolveClass(outputFormat, loader, OutputFormat.class));
    }
    if (partitioner != null) {
      job.setPartitionerClass(resolveClass(partitioner, loader, Partitioner.class));
    }
    if (sortComparator != null) {
      job.setSortComparatorClass(resolveClass(sortComparator, loader, RawComparator.class));
    }
    if (StringUtils.hasText(workingDir)) {
      job.setWorkingDirectory(new Path(workingDir));
    }
    if (jarClass != null) {
      job.setJarByClass(jarClass);
    }

    if (!CollectionUtils.isEmpty(inputPaths)) {
      for (String path : inputPaths) {
        FileInputFormat.addInputPath(job, new Path(path));
      }
    }

    if (StringUtils.hasText(outputPath)) {
      FileOutputFormat.setOutputPath(job, new Path(outputPath));
    }

    if (compressOutput != null) {
      FileOutputFormat.setCompressOutput(job, compressOutput);
    }

    if (codecClass != null) {
      FileOutputFormat.setOutputCompressorClass(
          job, resolveClass(codecClass, loader, CompressionCodec.class));
    }

    processJob(job);
  }