@Override
  public void delete(String id, boolean exact) {
    statsDelete++;

    String idMd5 = CacheFactory.createCacheKey(id);

    synchronized (this) {
      this.remove(idMd5);
    }

    if (cacheDir != null) new File(cacheDir, idMd5 + ".cache").delete();
  }
  @Override
  public void set(String id, cfData data, long ageMS, long idleTime) {
    statsSet++;

    String idMd5 = CacheFactory.createCacheKey(id);

    synchronized (this) {
      CacheUnit cu = new CacheUnit(id, data, ageMS);
      super.put(idMd5, cu);
    }

    // if we are spooling to disk, then we need to delete the one that is on disk
    if (cacheDir != null) new File(cacheDir, idMd5 + ".cache").delete();
  }
  @Override
  public cfData get(String id) {
    statsGet++;
    String idMd5 = CacheFactory.createCacheKey(id);

    // Check to see if in memory
    synchronized (this) {
      if (this.containsKey(idMd5)) {
        CacheUnit cu = super.get(idMd5);

        if (cu.stillYoung()) {
          statsHitMem++;
          return cu.val;
        } else {
          super.remove(idMd5);
          if (cacheDir != null) new File(cacheDir, idMd5 + ".cache").delete();

          statsMissAge++;
          return null;
        }
      }

      // Check to see if on disk
      if (cacheDir != null) {
        File fu = new File(cacheDir, idMd5 + ".cache");
        if (fu.isFile()) {

          CacheUnit cu = (CacheUnit) org.aw20.io.FileUtil.loadClass(fu);
          fu.delete();

          if (cu != null) {
            if (cu.stillYoung()) {
              super.put(idMd5, cu);
              statsHitDisk++;
              return cu.val;
            } else {
              statsMissAge++;
              return null;
            }
          }
        }
      }
    }

    // not found
    statsMiss++;
    return null;
  }