/**
   * Gets the specific attributes corresponding to the entryDN. This method obtains the DC Tree node
   * attributes and also performs compliance related verification checks in compliance mode. Note:
   * In compliance mode you can skip the compliance checks by setting ignoreCompliance to "false".
   *
   * @param token a valid SSOToken
   * @param entryDN the DN of the entry whose attributes need to retrieved
   * @param attrNames a Set of names of the attributes that need to be retrieved. The attrNames
   *     should not be null
   * @param ignoreCompliance a boolean value specificying if compliance related entries need to
   *     ignored or not. Ignored if true.
   * @return a Map containing attribute names as keys and Set of values corresponding to each key.
   * @throws AMException if an error is encountered in fetching the attributes
   */
  public Map getAttributes(
      SSOToken token,
      String entryDN,
      Set attrNames,
      boolean ignoreCompliance,
      boolean byteValues,
      int profileType)
      throws AMException, SSOException {
    if (attrNames == null || attrNames.isEmpty()) {
      return getAttributes(token, entryDN, ignoreCompliance, byteValues, profileType);
    }

    // Attributes are being requested; increment cache stats request counter
    cacheStats.incrementRequestCount(getSize());

    // Load the whole attrset in the cache, if in DCTree mode
    // Not good for performance, but fix later TODO (Deepa)
    if (dcTreeServicesImpl.isRequired()) { // TODO: This needs to be fixed!
      getAttributes(token, entryDN, ignoreCompliance, byteValues, profileType);
    }

    String principalDN = MiscUtils.getPrincipalDN(token);
    if (getDebug().messageEnabled()) {
      getDebug()
          .message(
              "In CachedRemoteServicesImpl.getAttributes("
                  + "SSOToken entryDN, attrNames, ignoreCompliance, "
                  + "byteValues) "
                  + "("
                  + principalDN
                  + ", "
                  + entryDN
                  + ", "
                  + attrNames
                  + ", "
                  + ignoreCompliance
                  + ", "
                  + byteValues
                  + " method.");
    }

    String dn = MiscUtils.formatToRFC(entryDN);
    CacheBlock cb = (CacheBlock) sdkCache.get(dn);
    if (cb == null) { // Entry not present in cache
      if (getDebug().messageEnabled()) {
        getDebug()
            .message(
                "CachedRemoteServicesImpl."
                    + "getAttributes():  NO entry found in Cache. Getting"
                    + " all these attributes from DS: "
                    + attrNames);
      }

      // If the attributes returned here have an empty set as value, then
      // such attributes do not have a value or invalid attributes.
      // Internally keep track of these attributes.
      AMHashMap attributes =
          (AMHashMap)
              super.getAttributes(
                  token, entryDN, attrNames, ignoreCompliance, byteValues, profileType);

      // These attributes are either not present or not found in DS.
      // Try to check if they need to be fetched by external
      // plugins
      Set missAttrNames = attributes.getMissingAndEmptyKeys(attrNames);
      cb = new CacheBlock(dn, true);
      cb.putAttributes(principalDN, attributes, missAttrNames, false, byteValues);
      sdkCache.put(dn, cb);

      if (!missAttrNames.isEmpty()) {
        attributes =
            getPluginAttrsAndUpdateCache(
                token,
                principalDN,
                entryDN,
                cb,
                attributes,
                missAttrNames,
                byteValues,
                profileType);
      }
      return attributes;
    } else { // Entry present in cache
      validateEntry(token, cb); // Entry may be an invalid entry
      AMHashMap attributes = (AMHashMap) cb.getAttributes(principalDN, attrNames, byteValues);

      // Find the missing attributes that need to be obtained from DS
      // Only find the missing keys as the ones with empty sets are not
      // found in DS
      Set missAttrNames = attributes.getMissingKeys(attrNames);
      if (!missAttrNames.isEmpty()) {
        boolean isComplete = cb.hasCompleteSet(principalDN);
        AMHashMap dsAttributes = null;
        if (!isComplete
            ||
            // Check for "nsRole" and "nsRoleDN" attributes
            missAttrNames.contains(NSROLEDN_ATTR)
            || missAttrNames.contains(NSROLE_ATTR)) {
          if (getDebug().messageEnabled()) {
            getDebug()
                .message(
                    "CachedRemoteServicesImpl."
                        + "getAttributes(): Trying to get these missing"
                        + " attributes from DS: "
                        + missAttrNames);
          }

          dsAttributes =
              (AMHashMap)
                  super.getAttributes(
                      token, entryDN, missAttrNames, ignoreCompliance, byteValues, profileType);

          if (dsAttributes != null) {
            attributes.putAll(dsAttributes);
            // Add these attributes, may be found in DS or just mark
            // as invalid (Attribute level Negative caching)
            Set newMissAttrNames = dsAttributes.getMissingAndEmptyKeys(missAttrNames);

            // Update dsAttributes with rest of the attributes
            // in cache
            dsAttributes.putAll(cb.getAttributes(principalDN, byteValues));

            // Update the cache
            cb.putAttributes(principalDN, dsAttributes, newMissAttrNames, isComplete, byteValues);
            missAttrNames = newMissAttrNames;
          }
        } else {
          // Update cache with invalid attributes
          cb.putAttributes(
              principalDN,
              cb.getAttributes(principalDN, byteValues),
              missAttrNames,
              isComplete,
              byteValues);
        }
        if (!missAttrNames.isEmpty()) {
          attributes =
              getPluginAttrsAndUpdateCache(
                  token,
                  principalDN,
                  entryDN,
                  cb,
                  attributes,
                  missAttrNames,
                  byteValues,
                  profileType);
        }
      } else { // All attributes found in cache
        if (getDebug().messageEnabled()) {
          getDebug()
              .message(
                  "CachedRemoteServicesImpl."
                      + "getAttributes():  found all attributes in "
                      + "Cache.");
        }
        cacheStats.updateHitCount(getSize());
      }
      // Remove all the empty values from the return attributes
      return attributes;
    }
  }
 private CachedRemoteServicesImpl() {
   super();
   initializeCache();
   cacheStats = CacheStats.createInstance(getClass().getName(), getDebug());
 }
  /**
   * Gets all attributes corresponding to the entryDN. This method obtains the DC Tree node
   * attributes and also performs compliance related verification checks in compliance mode. Note:
   * In compliance mode you can skip the compliance checks by setting ignoreCompliance to "false".
   *
   * @param token a valid SSOToken
   * @param entryDN the DN of the entry whose attributes need to retrieved
   * @param ignoreCompliance a boolean value specificying if compliance related entries need to
   *     ignored or not. Ignored if true.
   * @param byteValues if false StringValues are fetched, if true byte values are fetched.
   * @param profileType the oject type of entryDN
   * @return a Map containing attribute names as keys and Set of values corresponding to each key.
   * @throws AMException if an error is encountered in fetching the attributes
   */
  public Map getAttributes(
      SSOToken token, String entryDN, boolean ignoreCompliance, boolean byteValues, int profileType)
      throws AMException, SSOException {
    // Attributes are being requested; increment cache stats request counter
    cacheStats.incrementRequestCount(getSize());

    String principalDN = MiscUtils.getPrincipalDN(token);
    String dn = MiscUtils.formatToRFC(entryDN);

    if (getDebug().messageEnabled()) {
      getDebug()
          .message(
              "In CachedRemoteServicesImpl.getAttributes("
                  + "SSOToken entryDN, ignoreCompliance) "
                  + "("
                  + principalDN
                  + ", "
                  + entryDN
                  + ", "
                  + ignoreCompliance
                  + " method.");
    }

    CacheBlock cb = (CacheBlock) sdkCache.get(dn);
    AMHashMap attributes = null;
    if (cb != null) {
      validateEntry(token, cb);
      if (cb.hasCompleteSet(principalDN)) {
        cacheStats.updateHitCount(getSize());
        if (getDebug().messageEnabled()) {
          getDebug()
              .message(
                  "CachedRemoteServicesImpl."
                      + "getAttributes(): found all attributes in "
                      + "Cache.");
        }
        attributes = (AMHashMap) cb.getAttributes(principalDN, byteValues);
      } else { // Get the whole set from DS and store it;
        // ignore incomplete set
        if (getDebug().messageEnabled()) {
          getDebug()
              .message(
                  "CachedRemoteServicesImpl."
                      + "getAttributes():  complete attribute set NOT "
                      + "found in cache. Getting from DS.");
        }

        attributes =
            (AMHashMap)
                super.getAttributes(token, entryDN, ignoreCompliance, byteValues, profileType);
        cb.putAttributes(principalDN, attributes, null, true, byteValues);
      }
    } else { // Attributes not cached
      // Get all the attributes from DS and store them
      attributes =
          (AMHashMap)
              super.getAttributes(token, entryDN, ignoreCompliance, byteValues, profileType);
      cb = new CacheBlock(entryDN, true);
      cb.putAttributes(principalDN, attributes, null, true, byteValues);
      sdkCache.put(dn, cb);

      if (getDebug().messageEnabled()) {
        getDebug()
            .message(
                "CachedRemoteServicesImpl."
                    + "getAttributes(): attributes NOT found in cache. "
                    + "Fetched from DS.");
      }
    }

    // Get all external DS attributes by calling plugin modules.
    // Note these attributes should not be cached.
    Map extAttributes = getExternalAttributes(token, entryDN, null, profileType);
    if (extAttributes != null && !extAttributes.isEmpty()) {
      // Note the attributes stored in the cache are already copied to a
      // new map. Hence modifying this attributes is okay.
      if (getDebug().messageEnabled()) {
        getDebug()
            .message(
                "CachedRemoteServicesImpl."
                    + "getAttributes(): External attributes present. Adding"
                    + " them with original list");
      }
      attributes.putAll(extAttributes);
    }
    return attributes;
  }