protected <KIn, VIn, KOut, VOut> CollectableCollector<KOut, VOut> map(
      MapCombineCommand<KIn, VIn, KOut, VOut> mcc) throws InterruptedException {
    Cache<KIn, VIn> cache = cacheManager.getCache(mcc.getCacheName());
    Set<KIn> keys = mcc.getKeys();
    Set<KIn> inputKeysCopy = null;
    Mapper<KIn, VIn, KOut, VOut> mapper = mcc.getMapper();
    DistributionManager dm = cache.getAdvancedCache().getDistributionManager();
    boolean inputKeysSpecified = keys != null && !keys.isEmpty();
    Set<KIn> inputKeys = keys;
    if (!inputKeysSpecified) {
      inputKeys = filterLocalPrimaryOwner(cache.keySet(), dm);
    } else {
      inputKeysCopy = new HashSet<KIn>(keys);
    }
    // hook map function into lifecycle and execute it
    MapReduceTaskLifecycleService taskLifecycleService =
        MapReduceTaskLifecycleService.getInstance();
    DefaultCollector<KOut, VOut> collector = new DefaultCollector<KOut, VOut>();
    log.tracef("For m/r task %s invoking %s with input keys %s", mcc.getTaskId(), mcc, inputKeys);
    int interruptCount = 0;
    long start = log.isTraceEnabled() ? timeService.time() : 0;
    try {
      taskLifecycleService.onPreExecute(mapper, cache);
      for (KIn key : inputKeys) {
        if (checkInterrupt(interruptCount++) && Thread.currentThread().isInterrupted())
          throw new InterruptedException();

        VIn value = cache.get(key);
        mapper.map(key, value, collector);
        if (inputKeysSpecified) {
          inputKeysCopy.remove(key);
        }
      }
      Set<KIn> keysFromCacheLoader = null;
      if (inputKeysSpecified) {
        // load only specified remaining input keys - iff in CL and pinned to this primary owner
        keysFromCacheLoader = filterLocalPrimaryOwner(inputKeysCopy, dm);
      } else {
        // load everything from CL pinned to this primary owner
        keysFromCacheLoader =
            filterLocalPrimaryOwner(loadAllKeysFromCacheLoaderUsingFilter(inputKeys), dm);
      }
      log.tracef(
          "For m/r task %s cache loader input keys %s", mcc.getTaskId(), keysFromCacheLoader);
      interruptCount = 0;
      for (KIn key : keysFromCacheLoader) {
        if (checkInterrupt(interruptCount++) && Thread.currentThread().isInterrupted())
          throw new InterruptedException();

        VIn value = loadValueFromCacheLoader(key);
        if (value != null) {
          mapper.map(key, value, collector);
        }
      }
    } finally {
      if (log.isTraceEnabled()) {
        log.tracef(
            "Map phase for task %s took %s milliseconds",
            mcc.getTaskId(), timeService.timeDuration(start, TimeUnit.MILLISECONDS));
      }
      taskLifecycleService.onPostExecute(mapper);
    }
    return collector;
  }