docs: @Override
      @FFDCIgnore(InvalidNameException.class)
      public Root search(Root root) throws WIMException {
          final String METHODNAME = "search";
          boolean bFirstChangeSearchCall = false;
  
          String searchExpr = null;
  
          Map<String, Control> ctrlMap = ControlsHelper.getControlMap(root);
  
          // Extract the cache control
          CacheControl cacheCtrl = (CacheControl) ctrlMap.get(SchemaConstants.DO_CACHE_CONTROL);
  
          // If a cache control is passed, the cache should be cleared
          if (cacheCtrl != null) {
              String cacheMode = cacheCtrl.getMode();
              if (tc.isDebugEnabled()) {
                  Tr.debug(tc, METHODNAME + " Cache Control is passed with mode " + cacheMode);
              }
  
              // If the mode is 'clearAll', then invalidate the caches
              if (cacheMode != null && SchemaConstants.CACHE_MODE_CLEARALL.equalsIgnoreCase(cacheMode)) {
                  // Invalidate the attributes cache
                  iLdapConn.invalidateAttributeCache();
  
                  // Invalidate the search cache
                  iLdapConn.invalidateSearchCache();
  
                  // Log this clearAll call
                  String uniqueName = getCallerUniqueName();
                  if (tc.isWarningEnabled())
                      Tr.warning(tc, WIMMessageKey.CLEAR_ALL_CLEAR_CACHE_MODE, WIMMessageHelper.generateMsgParms(reposId, cacheMode, uniqueName));
              }
              // If the mode is 'clearEntity', then log it as not supported
              else if (cacheMode != null && SchemaConstants.CACHE_MODE_CLEAR_ENTITY.equalsIgnoreCase(cacheMode)) {
                  if (tc.isWarningEnabled())
                      Tr.warning(tc, WIMMessageKey.UNSUPPORTED_CLEAR_CACHE_MODE, WIMMessageHelper.generateMsgParms(reposId, cacheMode));
              } else if (tc.isWarningEnabled())
                  Tr.warning(tc, WIMMessageKey.UNKNOWN_CLEAR_CACHE_MODE, WIMMessageHelper.generateMsgParms(reposId, cacheMode));
          }
  
          // Check whether this is a search for changed entities
          boolean bChangeSearch = true;
          SearchControl searchControl = (ChangeControl) ctrlMap.get(SchemaConstants.DO_CHANGE_CONTROL);
          if (searchControl == null) {
              bChangeSearch = false;
              searchControl = (SearchControl) ctrlMap.get(SchemaConstants.DO_SEARCH_CONTROL);
          } else {
              List<CheckPointType> checkpoint = ((ChangeControl) searchControl).getCheckPoint();
              if (checkpoint.size() == 0) {
                  bFirstChangeSearchCall = true;
              }
          }
          if (searchControl != null) {
              searchExpr = searchControl.getExpression();
              if (!bFirstChangeSearchCall) {
                  if (searchExpr == null || searchExpr.length() == 0) {
                      throw new SearchControlException(WIMMessageKey.MISSING_SEARCH_EXPRESSION, Tr.formatMessage(
                                                                                                                 tc,
                                                                                                                 WIMMessageKey.MISSING_SEARCH_EXPRESSION,
                                                                                                                 null));
                  }
              }
          }
          // Check if the base entries were specified by the client
          // If specified by client, then don't override them with entity search base
          Set<String> realms = getSpecifiedRealms(root);
          boolean isSearchBaseSetByClient = realms.contains("n/a"); // Indicates search bases were set by client
  
          // read the custom property allowDNPrincipalNameAsLiteral
          String customProperty = getContextProperty(root, SchemaConstants.ALLOW_DN_PRINCIPALNAME_AS_LITERAL);
          boolean ignoreDNBaseSearch = customProperty.equalsIgnoreCase("true");
  
          LdapSearchControl srchCtrl = getLdapSearchControl(searchControl, isSearchBaseSetByClient, ignoreDNBaseSearch);
  
          String[] searchBases = srchCtrl.getBases();
          String sFilter = srchCtrl.getFilter();
          int countLimit = srchCtrl.getCountLimit();
          int timeLimit = srchCtrl.getTimeLimit();
          List<String> entityTypes = srchCtrl.getEntityTypes();
          List<String> propNames = srchCtrl.getPropertyNames();
          int scope = srchCtrl.getScope();
  
          Root outRoot = new Root();
  
          if (bChangeSearch) {
  
              ChangeControl changeControl = (ChangeControl) searchControl;
  
              /*
               * It is possible that we retrieve changes since the specified checkpoint and
               * while we process them more changes occur in the repository. As a result if
               * current checkpoint is retrieved after processing the changed entries, we
               * may lose some changes. Hence, we need to first retrieve the current checkpoint
               * and then process changed entities.
               */
  
              // Get the current checkpoint
              String checkPoint = changeHandler.getCurrentCheckPoint();
  
              CheckPointType currCheckPointDO = new CheckPointType();
              currCheckPointDO.setRepositoryId(reposId);
              currCheckPointDO.setRepositoryCheckPoint(checkPoint);
  
              ChangeResponseControl changeResponseCtrl = new ChangeResponseControl();
              changeResponseCtrl.getCheckPoint().add(currCheckPointDO);
  
              // Get changed entities since the checkpoint specified in ChangeControl
              List<CheckPointType> checkPointList = changeControl.getCheckPoint();
  
              if ((checkPointList != null) && (checkPointList.size() > 0)) {
                  /*
                   * At the adapter level, only the checkpoint corresponding to this
                   * repository is expected to be in the ChangeControl. Hence no need
                   * to check for repository ID
                   */
                  CheckPointType checkPointDO = checkPointList.get(0);
                  checkPoint = checkPointDO.getRepositoryCheckPoint();
                  if (checkPoint == null) {
                      throw new ChangeControlException(WIMMessageKey.NULL_CHECKPOINT_VALUE, Tr.formatMessage(
                                                                                                             tc,
                                                                                                             WIMMessageKey.NULL_CHECKPOINT_VALUE,
                                                                                                             null));
                  }
  
                  boolean isAttrCacheNotEmpty = true;
                  List<String> changeTypes = changeControl.getChangeTypes();
                  for (int i = 0; i < searchBases.length; i++) {
                      List<LdapEntry> ldapEntriesList = changeHandler.searchChangedEntities(checkPoint, changeTypes, searchBases[i],
                                                                                            sFilter, scope, entityTypes, propNames, countLimit, timeLimit);
  
                      if (ldapEntriesList != null) {
                          for (int j = 0; j < ldapEntriesList.size(); j++) {
                              LdapEntry entry = ldapEntriesList.get(j);
                              // invalidating the attribute cache
                              // checking for active directory so as to check if it is a deleted change type the fetch the DN from the unique name
                              if (isActiveDirectory) {
                                  if (SchemaConstants.CHANGETYPE_DELETE.equalsIgnoreCase(entry.getChangeType())) {
                                      iLdapConn.invalidateAttributes(iLdapConfigMgr.switchToLdapNode(entry.getUniqueName()), entry.getExtId(), entry.getUniqueName());
                                  } else {
                                      iLdapConn.invalidateAttributes(entry.getDN(), entry.getExtId(), entry.getUniqueName());
                                  }
                              } else {
                                  if (isAttrCacheNotEmpty) {//checking if attribute cache is invalidated.
                                      if (entry.getExtId() == null && SchemaConstants.CHANGETYPE_DELETE.equalsIgnoreCase(entry.getChangeType())) {
                                          iLdapConn.invalidateAttributeCache();// clear the cache if extId of deleted entry is not known
                                          isAttrCacheNotEmpty = false;
                                      } else {
                                          iLdapConn.invalidateAttributes(entry.getDN(), entry.getExtId(), entry.getUniqueName());
                                      }
                                  }
                              }
                              createEntityFromLdapEntry(outRoot, SchemaConstants.DO_ENTITIES, entry, propNames);
                          }
                          // invalidating the search cache
                          if (ldapEntriesList.size() > 0) {
                              iLdapConn.invalidateSearchCache();
                          }
                      }
                  }
              }
          } else {
              if (root.isSetContexts()) {
                  String inputPattern = getContextProperty(root, SchemaConstants.USE_USER_FILTER_FOR_SEARCH);
                  if (inputPattern != null && inputPattern.length() > 0) {
                      String dn = null;
                      try {
                          dn = new LdapName(inputPattern).toString();
                      } catch (InvalidNameException e) {
                          e.getMessage();
                      }
                      if (dn == null) {
                          Filter f = iLdapConfigMgr.getUserFilter();
                          if (f != null)
                              sFilter = f.prepare(inputPattern);
                      }
                  } else {
                      // Check if group filter pattern is specified
                      inputPattern = getContextProperty(root, SchemaConstants.USE_GROUP_FILTER_FOR_SEARCH);
                      if (inputPattern != null && inputPattern.length() > 0) {
                          String dn = null;
                          try {
                              dn = new LdapName(inputPattern).toString();
                          } catch (InvalidNameException e) {
                              e.getMessage();
                          }
                          if (dn == null) {
                              Filter f = iLdapConfigMgr.getGroupFilter();
                              if (f != null)
                                  sFilter = f.prepare(inputPattern);
                          }
                      }
                  }
              }
  
              for (int i = 0; i < searchBases.length; i++) {
                  Set<LdapEntry> ldapEntriesSet = iLdapConn.searchEntities(searchBases[i], sFilter, null, scope, entityTypes, propNames,
                                                                           false, false, countLimit, timeLimit);
                  for (LdapEntry entry : ldapEntriesSet) {
                      createEntityFromLdapEntry(outRoot, SchemaConstants.DO_ENTITIES, entry, propNames);
                  }
              }
          }
  
          return outRoot;
      }