/** show subject details */
  @SuppressWarnings("unchecked")
  public void memberMenuSubjectDetails() {

    GuiResponseJs guiResponseJs = GuiResponseJs.retrieveGuiResponseJs();

    // lets see which subject we are dealing with:
    HttpServletRequest httpServletRequest = GrouperUiFilter.retrieveHttpServletRequest();
    String menuIdOfMenuTarget = httpServletRequest.getParameter("menuIdOfMenuTarget");
    if (StringUtils.isBlank(menuIdOfMenuTarget)) {
      throw new RuntimeException("Missing id of menu target");
    }
    if (!menuIdOfMenuTarget.startsWith("memberMenuButton_")) {
      throw new RuntimeException("Invalid id of menu target: '" + menuIdOfMenuTarget + "'");
    }
    String memberId = GrouperUtil.prefixOrSuffix(menuIdOfMenuTarget, "memberMenuButton_", false);

    final Subject loggedInSubject = GrouperUiFilter.retrieveSubjectLoggedIn();

    GrouperSession grouperSession = null;

    try {

      grouperSession = GrouperSession.start(loggedInSubject);

      Member member = MemberFinder.findByUuid(grouperSession, memberId, true);
      Subject subject = member.getSubject();
      String order = null;

      try {
        order =
            GrouperUiConfig.retrieveConfig()
                .propertyValueString("subject.attributes.order." + subject.getSource().getId());
      } catch (MissingResourceException mre) {
        // thats ok, go with default
      }

      if (StringUtils.isBlank(order)) {
        Set<String> attributeNames = new LinkedHashSet<String>();
        attributeNames.add("screenLabel");
        attributeNames.addAll(GrouperUtil.nonNull(subject.getAttributes()).keySet());

        // lets add subjectId, typeName, sourceId, sourceName, memberId
        attributeNames.add("subjectId");
        attributeNames.add("name");
        attributeNames.add("description");
        attributeNames.add("typeName");
        attributeNames.add("sourceId");
        attributeNames.add("sourceName");

        order = GrouperUtil.join(attributeNames.iterator(), ',');
      }

      String[] attrNames = GrouperUtil.splitTrim(order, ",");

      SimpleMembershipUpdateContainer simpleMembershipUpdateContainer =
          SimpleMembershipUpdateContainer.retrieveFromSession();
      simpleMembershipUpdateContainer.setSubjectForDetails(subject);
      simpleMembershipUpdateContainer.getSubjectDetails().clear();

      // lookup each attribute
      for (String attrName : attrNames) {

        // sometimes group have blank attributes???
        if (StringUtils.isBlank(attrName)) {
          continue;
        }
        String attributeValue = GuiSubject.attributeValue(subject, attrName);
        simpleMembershipUpdateContainer.getSubjectDetails().put(attrName, attributeValue);
      }
      guiResponseJs.addAction(
          GuiScreenAction.newAlertFromJsp(
              "/WEB-INF/grouperUi/templates/simpleMembershipUpdate/simpleMembershipUpdateSubjectDetails.jsp"));

    } catch (ControllerDone cd) {
      throw cd;
    } catch (NoSessionException nse) {
      throw nse;
    } catch (Exception se) {
      throw new RuntimeException(
          "Error listing member details: " + menuIdOfMenuTarget + ", " + se.getMessage(), se);
    } finally {
      GrouperSession.stopQuietly(grouperSession);
    }
  }
  public ActionForward grouperExecute(
      ActionMapping mapping,
      ActionForm form,
      HttpServletRequest request,
      HttpServletResponse response,
      HttpSession session,
      GrouperSession grouperSession)
      throws Exception {
    Class sortOverrideClass = Group.class;
    NavExceptionHelper neh = getExceptionHelper(session);
    DynaActionForm subjectForm = (DynaActionForm) form;
    if ("true".equals(request.getParameter("changeMode")))
      PopulateSearchSubjectsAction.initMode(session);
    session.setAttribute("subtitle", "subject.action.show-summary");
    if (isEmpty(subjectForm.get("callerPageId"))) {
      if (isEmpty(subjectForm.get("subjectId"))) {
        LOG.info("Restoring lastSubjectSummaryForm");
        restoreDynaFormBean(session, subjectForm, "lastSubjectSummaryForm");
      } else {
        LOG.info("Saving lastSubjectSummaryForm");
        saveDynaFormBean(session, subjectForm, "lastSubjectSummaryForm");
        saveAsCallerPage(request, subjectForm);
      }
    }
    saveAsCallerPage(request, subjectForm);

    String listField = (String) subjectForm.get("listField");
    String membershipField = "members";

    if (!isEmpty(listField)) {
      membershipField = listField;
    }
    Field mField = null;
    try {
      mField = FieldFinder.find(membershipField, true);
    } catch (SchemaException e) {
      LOG.error("Could not find Field: " + membershipField, e);
      if ("members".equals(membershipField)) {
        LOG.fatal("Built in field: members, missing");
        throw new UnrecoverableErrorException(e);
      } else {
        mField = FieldFinder.find("members", true);
        request.setAttribute(
            "message", new Message("error.subject-summary.missing-field", listField, true));
      }
    }

    subjectForm.set("contextSubject", "true");
    String subjectId = (String) subjectForm.get("subjectId");
    String subjectType = (String) subjectForm.get("subjectType");
    String subjectSource = (String) subjectForm.get("sourceId");
    if (isEmpty(subjectId) || isEmpty(subjectType) || isEmpty(subjectSource)) {
      String msg =
          neh.missingParameters(
              subjectId, "subjectId", subjectType, "subjectType", subjectSource, "sourceId");
      LOG.error(msg);
      if (doRedirectToCaller(subjectForm)) {
        session.setAttribute(
            "sessionMessage", new Message("error.subject-summary.missing-parameter", true));
        return redirectToCaller(subjectForm);
      }
      throw new UnrecoverableErrorException("error.subject-summary.missing-parameter");
    }
    Subject subject = null;
    try {
      subject = SubjectFinder.findById(subjectId, subjectType, subjectSource, true);
    } catch (Exception e) {
      LOG.error(e);
      if (e instanceof SubjectNotFoundException) {
        subject = new UnresolvableSubject(subjectId, subjectType, subjectSource);
        addMessage(
            new Message(
                "error.subject.unresolvable", new String[] {subjectId, subjectSource}, true),
            request);
      } else {

        String contextError = "error.subject-summary.subject.exception";
        session.setAttribute("sessionMessage", new Message(neh.key(e), contextError, true));
        if (doRedirectToCaller(subjectForm)) return redirectToCaller(subjectForm);
        throw new UnrecoverableErrorException(contextError, e);
      }
    }
    Map subjectMap = GrouperHelper.subject2Map(subject);
    request.setAttribute("subject", subjectMap);

    String order = null;
    try {
      order =
          GrouperUiFilter.retrieveSessionMediaResourceBundle()
              .getString("subject.attributes.order." + subject.getSource().getId());
      request.setAttribute("subjectAttributeNames", order.split(","));
    } catch (Exception e) {
      // No order specified, so go with all, in whatever order they come
      List extendedAttr = new ArrayList(GrouperUtil.nonNull(subject.getAttributes()).keySet());
      extendedAttr.add("subjectType");
      extendedAttr.add("id");
      request.setAttribute("subjectAttributeNames", extendedAttr);
    }
    String membershipListScope = (String) subjectForm.get("membershipListScope");

    if ("any-access".equals(membershipListScope)) {
      if ("false".equals(request.getParameter("advancedSearch"))) {
        membershipListScope = null;
      } else {
        request.setAttribute("fromSubjectSummary", Boolean.TRUE);

        session.setAttribute("groupSearchSubject", subject);
        session.setAttribute("groupSearchSubjectMap", subjectMap);
        return mapping.findForward(FORWARD_GroupSearch);
      }
    }
    if (":all:imm:eff:access:naming:".indexOf(":" + membershipListScope + ":") == -1) {
      membershipListScope = (String) session.getAttribute("subjectMembershipListScope");
    }
    if (membershipListScope == null) membershipListScope = "imm";
    session.setAttribute("subjectMembershipListScope", membershipListScope);
    subjectForm.set("membershipListScope", membershipListScope);

    String accessPriv = (String) subjectForm.get("accessPriv");
    if (isEmpty(accessPriv)) accessPriv = (String) session.getAttribute("subjectSummaryAccessPriv");
    if (isEmpty(accessPriv)) accessPriv = "read";
    session.setAttribute("subjectSummaryAccessPriv", accessPriv);
    subjectForm.set("accessPriv", accessPriv);

    String namingPriv = (String) subjectForm.get("namingPriv");
    if (isEmpty(namingPriv)) namingPriv = (String) session.getAttribute("subjectSummaryNamingPriv");
    if (isEmpty(namingPriv)) namingPriv = "create";
    session.setAttribute("subjectSummaryNamingPriv", namingPriv);
    subjectForm.set("namingPriv", namingPriv);
    // Retrieve the membership according to scope selected by user
    Member member = null;
    try {
      member = MemberFinder.findBySubject(grouperSession, subject, true);
      if (member == null) {
        throw new MemberNotFoundException("Unresolvable subject is also not a Member");
      }
    } catch (Exception e) {
      LOG.error(e);
      if (doRedirectToCaller(subjectForm)) {
        session.setAttribute(
            "sessionMessage", new Message("error.subject-summary.member.exception", true));
        return redirectToCaller(subjectForm);
      }
      throw new UnrecoverableErrorException("error.subject-summary.member.exception", e);
    }
    Set subjectScopes = null;
    List subjectScopeMaps = null;
    Map listViews = new HashMap();
    listViews.put("titleKey", "subject.summary.memberships");
    listViews.put("noResultsKey", "subject.list-membership.none");
    listViews.put("view", "whereSubjectsAreMembers");
    // listViews.put("itemView","whereIsMemberLink");
    listViews.put("itemView", "subjectSummary");
    listViews.put("headerView", "genericListHeader");
    listViews.put("footerView", "genericListFooter");

    if ("imm".equals(membershipListScope)) {
      subjectScopes = member.getImmediateMemberships(mField);
      listViews.put("noResultsKey", "subject.list-membership.imm.none");
    } else if ("eff".equals(membershipListScope)) {
      if (membershipField.equals("members")) {
        subjectScopes = member.getMemberships();
        subjectScopes.removeAll(member.getImmediateMemberships());

      } else {
        subjectScopes = member.getEffectiveMemberships(mField);
        listViews.put("noResultsKey", "subject.list-membership.all.none");
      }
      if ("members".equals(membershipField)) {
        listViews.put("noResultsKey", "subject.list-membership.eff.none");
      } else {
        listViews.put("noResultsKey", "subject.list-membership.custom.eff.none");
      }

    } else if ("all".equals(membershipListScope)) {
      subjectScopes = member.getMemberships(mField);
      listViews.put("noResultsKey", "subject.list-membership.all.none");
    } else if ("access".equals(membershipListScope)) {

      subjectScopes = GrouperHelper.getGroupsOrStemsWhereMemberHasPriv(member, accessPriv);

      // filter out groups where the subject can't see privs
      removeObjectsNotAllowedToSeePrivs(subjectScopes);

      subjectScopeMaps =
          GrouperHelper.subjects2SubjectPrivilegeMaps(
              grouperSession, subjectScopes, subject, accessPriv);
      listViews.put("titleKey", "subject.summary.access-privs");
      listViews.put("noResultsKey", "subject.list-access.none");
      listViews.put("view", "subjectSummaryPrivileges");
      listViews.put("itemView", "subjectSummaryPrivilege");
    } else {
      sortOverrideClass = Stem.class;
      subjectScopes = GrouperHelper.getGroupsOrStemsWhereMemberHasPriv(member, namingPriv);

      // filter out stems where the subject can't see privs
      removeObjectsNotAllowedToSeePrivs(subjectScopes);

      subjectScopeMaps =
          GrouperHelper.subjects2SubjectPrivilegeMaps(
              grouperSession, subjectScopes, subject, namingPriv);
      listViews.put("titleKey", "subject.summary.naming-privs");
      listViews.put("noResultsKey", "subject.list-naming.none");
      listViews.put("view", "subjectSummaryPrivileges");
      listViews.put("itemView", "subjectSummaryPrivilege");
    }
    request.setAttribute("scopeListData", listViews);
    if (subjectScopeMaps == null) {
      Map countMap = new HashMap();
      Map sources = new HashMap();
      List uniqueSubjectScopes =
          GrouperHelper.getOneMembershipPerSubjectOrGroup(
              subjectScopes, "subject", countMap, sources, 0);
      subjectScopeMaps = GrouperHelper.memberships2Maps(grouperSession, uniqueSubjectScopes);
      GrouperHelper.setMembershipCountPerSubjectOrGroup(subjectScopeMaps, "subject", countMap);
    }
    // This is a hack to force sorting by Group/Stem rather than Subject - which is generally what
    // happens with Memberships
    // DefaultComparatorImpl has been updated to read the TheadLocal
    UIThreadLocal.put("GrouperComparatorHelperOverrideClass", sortOverrideClass);
    subjectScopeMaps = sort(subjectScopeMaps, request, "subjectSummary", -1, null);
    UIThreadLocal.replace("GrouperComparatorHelperOverrideClass", null);
    String startStr = (String) subjectForm.get("start");
    if (startStr == null || "".equals(startStr)) startStr = "0";

    int start = Integer.parseInt(startStr);
    int pageSize = getPageSize(session);
    int end = start + pageSize;
    if (end > subjectScopeMaps.size()) end = subjectScopeMaps.size();
    CollectionPager pager =
        new CollectionPager(
            null, subjectScopeMaps, subjectScopeMaps.size(), null, start, null, pageSize);

    if (!isEmpty(listField)) pager.setParam("listField", listField);
    pager.setParam("subjectId", subjectId);
    pager.setParam("subjectType", subjectType);
    pager.setParam("sourceId", subjectSource);
    pager.setParam("returnTo", subjectForm.get("returnTo"));
    pager.setParam("returnToLinkKey", subjectForm.get("returnToLinkKey"));
    pager.setTarget(mapping.getPath());
    request.setAttribute("pager", pager);
    request.setAttribute("linkParams", pager.getParams().clone());
    request.setAttribute("listFieldParams", pager.getParams().clone());

    Map saveParams = new HashMap();
    saveParams.put("subjectId", subject.getId());
    saveParams.put("subjectType", subject.getType().getName());
    saveParams.put("sourceId", subject.getSource().getId());
    saveParams.put("callerPageId", request.getAttribute("thisPageId"));
    request.setAttribute("saveParams", saveParams);

    if (subjectType.equals("group")) {
      List lists = GrouperHelper.getReadableListFieldsForGroup(grouperSession, subjectId);
      if (!lists.isEmpty()) request.setAttribute("listFields", lists);
    }

    List memberOfListFields = GrouperHelper.getListFieldsForSubject(grouperSession, subject);
    if (memberOfListFields.size() > 0) {
      request.setAttribute("memberOfListFields", memberOfListFields);
    }

    Collection accessPrivs =
        GrouperHelper.getGroupPrivsWithLabels(GrouperUiFilter.retrieveSessionNavResourceBundle());
    Collection namingPrivs =
        GrouperHelper.getStemPrivsWithLabels(GrouperUiFilter.retrieveSessionNavResourceBundle());
    request.setAttribute("allAccessPrivs", accessPrivs);
    request.setAttribute("allNamingPrivs", namingPrivs);

    return mapping.findForward(FORWARD_SubjectSummary);
  }
  /**
   * @see
   *     edu.internet2.middleware.grouper.changeLog.ChangeLogConsumerBase#processChangeLogEntries(java.util.List,
   *     edu.internet2.middleware.grouper.changeLog.ChangeLogProcessorMetadata)
   */
  @Override
  public long processChangeLogEntries(
      List<ChangeLogEntry> changeLogEntryList,
      ChangeLogProcessorMetadata changeLogProcessorMetadata) {

    long currentId = -1;

    boolean startedGrouperSession = false;
    GrouperSession grouperSession = GrouperSession.staticGrouperSession(false);
    if (grouperSession == null) {
      grouperSession = GrouperSession.startRootSession();
      startedGrouperSession = true;
    } else {
      grouperSession = grouperSession.internal_getRootSession();
    }

    // try catch so we can track that we made some progress
    try {
      for (ChangeLogEntry changeLogEntry : changeLogEntryList) {
        currentId = changeLogEntry.getSequenceNumber();

        // if this is a group add action and category
        if (changeLogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.GROUP_ADD)) {

          String groupName = changeLogEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_ADD.name);
          if (GrouperDuoUtils.validDuoGroupName(groupName)) {
            String groupExtension = GrouperUtil.extensionFromName(groupName);
            // get the group in grouper
            String groupDescription =
                changeLogEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_ADD.description);
            // shouldnt be the case but check anyways
            if (!GrouperDuoCommands.retrieveGroups().containsKey(groupExtension)) {
              GrouperDuoCommands.createDuoGroup(groupExtension, groupDescription, true);
            }
          }
        } else if (changeLogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.GROUP_DELETE)) {
          String groupName =
              changeLogEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_DELETE.name);
          if (GrouperDuoUtils.validDuoGroupName(groupName)) {
            String groupExtension = GrouperUtil.extensionFromName(groupName);
            // shouldnt be the case but check anyways
            GrouperDuoGroup grouperDuoGroup =
                GrouperDuoCommands.retrieveGroups().get(groupExtension);
            if (grouperDuoGroup != null) {
              GrouperDuoCommands.deleteDuoGroup(grouperDuoGroup.getId(), true);
            }
          }
        }
        if (changeLogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.GROUP_UPDATE)) {
          String groupName =
              changeLogEntry.retrieveValueForLabel(ChangeLogLabels.GROUP_UPDATE.name);
          if (GrouperDuoUtils.validDuoGroupName(groupName)) {
            String groupExtension = GrouperUtil.extensionFromName(groupName);
            // get the group in grouper

            Group group = GroupFinder.findByName(grouperSession, groupName, false);

            if (group != null) {

              // shouldnt be the case but check anyways
              Map<String, GrouperDuoGroup> groupNameToDuoGroupMap =
                  GrouperDuoCommands.retrieveGroups();
              GrouperDuoGroup grouperDuoGroup = groupNameToDuoGroupMap.get(groupExtension);
              if (grouperDuoGroup != null) {
                GrouperDuoCommands.updateDuoGroup(
                    grouperDuoGroup.getId(), group.getDescription(), true);
              }
            }
          }
        }

        boolean isMembershipAdd =
            changeLogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.MEMBERSHIP_ADD);
        boolean isMembershipDelete =
            changeLogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.MEMBERSHIP_DELETE);
        boolean isMembershipUpdate =
            changeLogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.MEMBERSHIP_UPDATE);
        if (isMembershipAdd || isMembershipDelete || isMembershipUpdate) {
          String groupName =
              changeLogEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.groupName);

          if (GrouperDuoUtils.validDuoGroupName(groupName)) {
            String sourceId =
                changeLogEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.sourceId);

            boolean inCorrectSubjectSource =
                GrouperDuoUtils.configSourcesForSubjects().contains(sourceId);

            if (inCorrectSubjectSource) {
              String groupExtension = GrouperUtil.extensionFromName(groupName);
              Group group = GroupFinder.findByName(grouperSession, groupName, false);
              Map<String, GrouperDuoGroup> groupNameToDuoGroupMap =
                  GrouperDuoCommands.retrieveGroups();
              GrouperDuoGroup grouperDuoGroup = groupNameToDuoGroupMap.get(groupExtension);
              String subjectId =
                  changeLogEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.subjectId);

              String subjectAttributeForDuoUsername =
                  GrouperDuoUtils.configSubjectAttributeForDuoUsername();

              String username = null;
              Subject subject = SubjectFinder.findByIdAndSource(subjectId, sourceId, false);

              if (StringUtils.equals("id", subjectAttributeForDuoUsername)) {
                username = subjectId;
              } else {

                if (subject != null) {
                  String attributeValue = subject.getAttributeValue(subjectAttributeForDuoUsername);
                  if (!StringUtils.isBlank(attributeValue)) {
                    username = attributeValue;
                  }
                }
              }

              String duoGroupId = grouperDuoGroup != null ? grouperDuoGroup.getId() : null;
              String duoUserId =
                  !StringUtils.isBlank(username)
                      ? GrouperDuoCommands.retrieveUserIdFromUsername(username)
                      : null;

              // cant do anything if missing these things
              if (!StringUtils.isBlank(duoGroupId) && !StringUtils.isBlank(duoUserId)) {

                boolean userInDuoGroup =
                    GrouperDuoCommands.userInGroup(duoUserId, duoGroupId, true);

                boolean addUserToGroup = isMembershipAdd;

                // if update it could have unexpired
                if (isMembershipUpdate
                    && group != null
                    && subject != null
                    && group.hasMember(subject)) {
                  addUserToGroup = true;
                }

                // see if any update is needed
                if (addUserToGroup != userInDuoGroup) {
                  if (addUserToGroup) {
                    GrouperDuoCommands.assignUserToGroup(duoUserId, duoGroupId, true);
                  } else {
                    GrouperDuoCommands.removeUserFromGroup(duoUserId, duoGroupId, true);
                  }
                }
              }
            }
          }
        }

        // we successfully processed this record
      }
    } catch (Exception e) {
      changeLogProcessorMetadata.registerProblem(e, "Error processing record", currentId);
      // we made it to this -1
      return currentId - 1;
    } finally {
      if (startedGrouperSession) {
        GrouperSession.stopQuietly(grouperSession);
      }
    }
    if (currentId == -1) {
      throw new RuntimeException("Couldnt process any records");
    }

    return currentId;
  }
  /**
   * see if a group or parent or ancestor folder has an attribute. this can run securely.
   *
   * @param owner
   * @param attributeFlag
   * @param attributeFlagName
   * @param subjectMakingCall subject making this call
   * @return true if group or stem has attribute
   */
  private boolean hasAttributeOrAncestorHasAttributeHelper(
      AttributeAssignable owner,
      AttributeDefName attributeFlag,
      String attributeFlagName,
      Subject subjectMakingCall) {

    // not sure why would be null
    if (owner == null) {
      return false;
    }

    // key to cache is type of object, name, and attribute name
    String type = null;

    if (owner instanceof Group) {
      type = "group";
    } else if (owner instanceof Stem) {
      type = "stem";
    } else if (owner instanceof AttributeDef) {
      type = "attributeDef";
    } else {
      throw new RuntimeException(
          "hasAttributeOrAncestorHasAttribute() is only available for Groups, Stems, or AttributeDefs, not "
              + owner.getClass().getName());
    }

    MultiKey key =
        new MultiKey(
            type,
            ((GrouperObject) owner).getName(),
            attributeFlagName,
            subjectMakingCall.getSourceId(),
            subjectMakingCall.getId());

    Boolean result = objectHasAttributeCache().get(key);

    if (result == null) {

      // lazy load the attribute only once
      attributeFlag =
          attributeFlag != null
              ? attributeFlag
              : AttributeDefNameFinder.findByName(attributeFlagName, false);

      if (attributeFlag == null) {
        // cant see it cant read it
        result = false;
      } else {
        result = this.hasAttribute(attributeFlag);

        if (!result) {
          // see if the parent stem or ancestor has the attribute
          AttributeAssignable parent = null;
          if (owner instanceof Group) {
            parent = ((Group) owner).getParentStem();
          } else if (owner instanceof Stem) {
            // cant go further than root
            if (!((Stem) owner).isRootStem()) {
              parent = ((Stem) owner).getParentStem();
            } else {
              result = false;
            }
          } else if (owner instanceof AttributeDef) {
            parent = ((AttributeDef) owner).getParentStem();
          } else {
            throw new RuntimeException(
                "hasAttributeOrAncestorHasAttribute() is only available for Groups, Stems, or AttributeDefs, not "
                    + owner.getClass().getName());
          }
          if (parent != null) {
            result =
                parent
                    .getAttributeDelegate()
                    .hasAttributeOrAncestorHasAttributeHelper(
                        parent, attributeFlag, attributeFlagName, subjectMakingCall);
          }
        }
      }

      objectHasAttributeCache().put(key, result);
    }

    return result;
  }