All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.ldap.sdk.unboundidds.controls.MatchingEntryCountResponseControl Maven / Gradle / Ivy

/*
 * Copyright 2014-2022 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2014-2022 Ping Identity Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2014-2022 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.ldap.sdk.unboundidds.controls;



import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.unboundid.asn1.ASN1Boolean;
import com.unboundid.asn1.ASN1Element;
import com.unboundid.asn1.ASN1Integer;
import com.unboundid.asn1.ASN1Null;
import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.asn1.ASN1Sequence;
import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.DecodeableControl;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;
import com.unboundid.util.json.JSONArray;
import com.unboundid.util.json.JSONBoolean;
import com.unboundid.util.json.JSONField;
import com.unboundid.util.json.JSONNumber;
import com.unboundid.util.json.JSONObject;
import com.unboundid.util.json.JSONString;
import com.unboundid.util.json.JSONValue;

import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;



/**
 * This class provides a response control that may be used to provide
 * information about the number of entries that match a given set of search
 * criteria.  The control will be included in the search result done message
 * for any successful search operation in which the request contained a matching
 * entry count request control.
 * 
*
* NOTE: This class, and other classes within the * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only * supported for use against Ping Identity, UnboundID, and * Nokia/Alcatel-Lucent 8661 server products. These classes provide support * for proprietary functionality or for external specifications that are not * considered stable or mature enough to be guaranteed to work in an * interoperable way with other types of LDAP servers. *
*
* The matching entry count response control has an OID of * "1.3.6.1.4.1.30221.2.5.37", a criticality of false, and a value with the * following encoding: *
 *   MatchingEntryCountResponse ::= SEQUENCE {
 *        entryCount               CHOICE {
 *             examinedCount            [0] INTEGER,
 *             unexaminedCount          [1] INTEGER,
 *             upperBound               [2] INTEGER,
 *             unknown                  [3] NULL,
 *             ... }
 *        debugInfo                [0] SEQUENCE OF OCTET STRING OPTIONAL,
 *        searchIndexed            [1] BOOLEAN DEFAULT TRUE,
 *        shortCircuited           [2] BOOLEAN OPTIONAL,
 *        fullyIndexed             [3] BOOLEAN OPTIONAL,
 *        candidatesAreInScope     [4] BOOLEAN OPTIONAL,
 *        remainingFilter          [5] Filter OPTIONAL,
 *        ... }
 * 
* * @see MatchingEntryCountRequestControl */ @NotMutable() @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class MatchingEntryCountResponseControl extends Control implements DecodeableControl { /** * The OID (1.3.6.1.4.1.30221.2.5.37) for the matching entry count response * control. */ @NotNull public static final String MATCHING_ENTRY_COUNT_RESPONSE_OID = "1.3.6.1.4.1.30221.2.5.37"; /** * The BER type for the element used to hold the list of debug messages. */ private static final byte TYPE_DEBUG_INFO = (byte) 0xA0; /** * The BER type for the element used to indicate whether the search criteria * is at least partially indexed. */ private static final byte TYPE_SEARCH_INDEXED = (byte) 0x81; /** * The BER type for the element used to indicate whether the server * short-circuited during candidate set processing before evaluating all * elements of the search criteria (the filter and scope). */ private static final byte TYPE_SHORT_CIRCUITED = (byte) 0x82; /** * The BER type for the element used to indicate whether the search criteria * is fully indexed. */ private static final byte TYPE_FULLY_INDEXED = (byte) 0x83; /** * The BER type for the element used to indicate whether all the identified * candidate entries are within the scope of the search. */ private static final byte TYPE_CANDIDATES_ARE_IN_SCOPE = (byte) 0x84; /** * The BER type for the element used to provide the remaining filter for the * search operation, which is the portion of the filter that was determined * to be unindexed, or that was unevaluated if processing short-circuited in * the course of building the candidate set. */ private static final byte TYPE_REMAINING_FILTER = (byte) 0xA5; /** * The name of the field used to hold the candidates are in scope flag in the * JSON representation of this control. */ @NotNull private static final String JSON_FIELD_CANDIDATES_ARE_IN_SCOPE = "candidates-are-in-scope"; /** * The name of the field used to hold the count type in the JSON * representation of this control. */ @NotNull private static final String JSON_FIELD_COUNT_TYPE = "count-type"; /** * The name of the field used to hold the count value in the JSON * representation of this control. */ @NotNull private static final String JSON_FIELD_COUNT_VALUE = "count-value"; /** * The name of the field used to hold the debug info in the JSON * representation of this control. */ @NotNull private static final String JSON_FIELD_DEBUG_INFO = "debug-info"; /** * The name of the field used to hold the fully indexed flag in the JSON * representation of this control. */ @NotNull private static final String JSON_FIELD_FULLY_INDEXED = "fully-indexed"; /** * The name of the field used to hold the remaining filter in the JSON * representation of this control. */ @NotNull private static final String JSON_FIELD_REMAINING_FILTER = "remaining-filter"; /** * The name of the field used to hold the search indexed flag in the JSON * representation of this control. */ @NotNull private static final String JSON_FIELD_SEARCH_INDEXED = "search-indexed"; /** * The name of the field used to hold the short-circuited flag in the JSON * representation of this control. */ @NotNull private static final String JSON_FIELD_SHORT_CIRCUITED = "short-circuited"; /** * The result-type value that will be used for an examined count in the JSON * representation of this control. */ @NotNull private static final String JSON_COUNT_TYPE_EXAMINED_COUNT = "examined-count"; /** * The result-type value that will be used for an unexamined count in the JSON * representation of this control. */ @NotNull private static final String JSON_COUNT_TYPE_UNEXAMINED_COUNT = "unexamined-count"; /** * The result-type value that will be used for an unknown count in the JSON * representation of this control. */ @NotNull private static final String JSON_COUNT_TYPE_UNKNOWN = "unknown"; /** * The result-type value that will be used for an upper-bound count in the * JSON representation of this control. */ @NotNull private static final String JSON_COUNT_TYPE_UPPER_BOUND = "upper-bound"; /** * The serial version UID for this serializable class. */ private static final long serialVersionUID = -7808452580964236458L; // Indicates whether the search criteria is considered at least partially // indexed by the server. private final boolean searchIndexed; // Indicates whether all the identified candidate entries are within the scope // of the search. @Nullable private final Boolean candidatesAreInScope; // Indicates whether the search criteria is considered fully indexed. @Nullable private final Boolean fullyIndexed; // Indicates whether the server short-circuited during candidate set // processing before evaluating all elements of the search criteria (the // filter and scope). @Nullable private final Boolean shortCircuited; // The portion of the filter that was either identified as unindexed or that // was not evaluated in the course of building the candidate set. @Nullable private final Filter remainingFilter; // The count value for this matching entry count response control. private final int countValue; // A list of messages providing debug information about the processing // performed by the server. @NotNull private final List debugInfo; // The count type for this matching entry count response control. @NotNull private final MatchingEntryCountType countType; /** * Creates a new empty control instance that is intended to be used only for * decoding controls via the {@code DecodeableControl} interface. */ MatchingEntryCountResponseControl() { searchIndexed = false; candidatesAreInScope = null; fullyIndexed = null; shortCircuited = null; remainingFilter = null; countValue = -1; countType = null; debugInfo = null; } /** * Creates a new matching entry count response control with the provided * information. * * @param countType The matching entry count type. It must not * be {@code null}. * @param countValue The matching entry count value. It must be * greater than or equal to zero for a count * type of either {@code EXAMINED_COUNT} or * {@code UNEXAMINED_COUNT}. It must be greater * than zero for a count type of * {@code UPPER_BOUND}. It must be -1 for a * count type of {@code UNKNOWN}. * @param searchIndexed Indicates whether the search criteria is * considered at least partially indexed and * could be processed more efficiently than * examining all entries with a full database * scan. * @param shortCircuited Indicates whether the server short-circuited * during candidate set processing before * evaluating all elements of the search * criteria (the filter and scope). This may be * {@code null} if it is not available (e.g., * because extended response data was not * requested). * @param fullyIndexed Indicates whether the search is considered * fully indexed. Note that this may be * {@code false} even if the filter is actually * fully indexed if server index processing * short-circuited before evaluating all * components of the filter. To avoid this, * issue the request control with both fast and * slow short-circuit thresholds set to zero. * This may be {@code null} if this is not * available (e.g., because extended response * data was not requested). * @param candidatesAreInScope Indicates whether all the identified * candidate entries are within the scope of * the search. It may be {@code null} if this * is not available (e.g., because extended * response data was not requested). * @param remainingFilter The portion of the filter that was either * identified as unindexed or that was not * evaluated because processing short-circuited * in the course of building the candidate set. * It may be {@code null} if there is no * remaining filter or if this information is * not available (e.g., because extended * response data was not requested). * @param debugInfo An optional list of messages providing debug * information about the processing performed by * the server. It may be {@code null} or empty * if no debug messages should be included. */ private MatchingEntryCountResponseControl( @NotNull final MatchingEntryCountType countType, final int countValue, final boolean searchIndexed, @Nullable final Boolean shortCircuited, @Nullable final Boolean fullyIndexed, @Nullable final Boolean candidatesAreInScope, @Nullable final Filter remainingFilter, @Nullable final Collection debugInfo) { super(MATCHING_ENTRY_COUNT_RESPONSE_OID, false, encodeValue(countType, countValue, searchIndexed, shortCircuited, fullyIndexed, candidatesAreInScope, remainingFilter, debugInfo)); this.countType = countType; this.countValue = countValue; this.searchIndexed = searchIndexed; this.shortCircuited = shortCircuited; this.fullyIndexed = fullyIndexed; this.candidatesAreInScope = candidatesAreInScope; this.remainingFilter = remainingFilter; if (debugInfo == null) { this.debugInfo = Collections.emptyList(); } else { this.debugInfo = Collections.unmodifiableList(new ArrayList<>(debugInfo)); } } /** * Creates a new matching entry count response control decoded from the given * generic control contents. * * @param oid The OID for the control. * @param isCritical Indicates whether this control should be marked * critical. * @param value The encoded value for the control. * * @throws LDAPException If a problem occurs while attempting to decode the * generic control as a matching entry count response * control. */ public MatchingEntryCountResponseControl(@NotNull final String oid, final boolean isCritical, @Nullable final ASN1OctetString value) throws LDAPException { super(oid, isCritical, value); if (value == null) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_MISSING_VALUE.get()); } try { final ASN1Element[] elements = ASN1Sequence.decodeAsSequence(value.getValue()).elements(); countType = MatchingEntryCountType.valueOf(elements[0].getType()); if (countType == null) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_INVALID_COUNT_TYPE.get( StaticUtils.toHex(elements[0].getType()))); } switch (countType) { case EXAMINED_COUNT: case UNEXAMINED_COUNT: countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue(); if (countValue < 0) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_NEGATIVE_EXACT_COUNT.get()); } break; case UPPER_BOUND: countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue(); if (countValue <= 0) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_NON_POSITIVE_UPPER_BOUND. get()); } break; case UNKNOWN: default: countValue = -1; break; } boolean decodedSearchIndexed = (countType != MatchingEntryCountType.UNKNOWN); Boolean decodedFullyIndexed = null; Boolean decodedCandidatesAreInScope = null; Boolean decodedShortCircuited = null; Filter decodedRemainingFilter = null; List debugMessages = Collections.emptyList(); for (int i=1; i < elements.length; i++) { switch (elements[i].getType()) { case TYPE_DEBUG_INFO: final ASN1Element[] debugElements = ASN1Sequence.decodeAsSequence(elements[i]).elements(); debugMessages = new ArrayList<>(debugElements.length); for (final ASN1Element e : debugElements) { debugMessages.add( ASN1OctetString.decodeAsOctetString(e).stringValue()); } break; case TYPE_SEARCH_INDEXED: decodedSearchIndexed = ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); break; case TYPE_SHORT_CIRCUITED: decodedShortCircuited = ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); break; case TYPE_FULLY_INDEXED: decodedFullyIndexed = ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); break; case TYPE_CANDIDATES_ARE_IN_SCOPE: decodedCandidatesAreInScope = ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); break; case TYPE_REMAINING_FILTER: final ASN1Element filterElement = ASN1Element.decode(elements[i].getValue()); decodedRemainingFilter = Filter.decode(filterElement); break; } } searchIndexed = decodedSearchIndexed; shortCircuited = decodedShortCircuited; fullyIndexed = decodedFullyIndexed; candidatesAreInScope = decodedCandidatesAreInScope; remainingFilter = decodedRemainingFilter; debugInfo = Collections.unmodifiableList(debugMessages); } catch (final LDAPException le) { Debug.debugException(le); throw le; } catch (final Exception e) { Debug.debugException(e); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_GET_BACKEND_SET_ID_RESPONSE_CANNOT_DECODE.get( StaticUtils.getExceptionMessage(e)), e); } } /** * Creates a new matching entry count response control for the case in which * the exact number of matching entries is known. * * @param count The exact number of entries matching the associated * search criteria. It must be greater than or equal to * zero. * @param examined Indicates whether the server examined the entries to * exclude those entries that would not be returned to the * client in a normal search with the same criteria. * @param debugInfo An optional list of messages providing debug information * about the processing performed by the server. It may be * {@code null} or empty if no debug messages should be * included. * * @return The matching entry count response control that was created. */ @NotNull() public static MatchingEntryCountResponseControl createExactCountResponse( final int count, final boolean examined, @Nullable final Collection debugInfo) { return createExactCountResponse(count, examined, true, debugInfo); } /** * Creates a new matching entry count response control for the case in which * the exact number of matching entries is known. * * @param count The exact number of entries matching the associated * search criteria. It must be greater than or equal * to zero. * @param examined Indicates whether the server examined the entries to * exclude those entries that would not be returned to * the client in a normal search with the same * criteria. * @param searchIndexed Indicates whether the search criteria is considered * at least partially indexed and could be processed * more efficiently than examining all entries with a * full database scan. * @param debugInfo An optional list of messages providing debug * information about the processing performed by the * server. It may be {@code null} or empty if no debug * messages should be included. * * @return The matching entry count response control that was created. */ @NotNull() public static MatchingEntryCountResponseControl createExactCountResponse( final int count, final boolean examined, final boolean searchIndexed, @Nullable final Collection debugInfo) { return createExactCountResponse(count, examined, searchIndexed, null, null, null, null, debugInfo); } /** * Creates a new matching entry count response control for the case in which * the exact number of matching entries is known. * * @param count The exact number of entries matching the * associated search criteria. It must be * greater than or equal to zero. * @param examined Indicates whether the server examined the * entries to exclude those entries that would * not be returned to the client in a normal * search with the same criteria. * @param searchIndexed Indicates whether the search criteria is * considered at least partially indexed and * could be processed more efficiently than * examining all entries with a full database * scan. * @param shortCircuited Indicates whether the server short-circuited * during candidate set processing before * evaluating all elements of the search * criteria (the filter and scope). This may be * {@code null} if it is not available (e.g., * because extended response data was not * requested). * @param fullyIndexed Indicates whether the search is considered * fully indexed. Note that this may be * {@code false} even if the filter is actually * fully indexed if server index processing * short-circuited before evaluating all * components of the filter. To avoid this, * issue the request control with both fast and * slow short-circuit thresholds set to zero. * This may be {@code null} if this is not * available (e.g., because extended response * data was not requested). * @param candidatesAreInScope Indicates whether all the identified * candidate entries are within the scope of * the search. It may be {@code null} if this * is not available (e.g., because extended * response data was not requested). * @param remainingFilter The portion of the filter that was either * identified as unindexed or that was not * evaluated because processing short-circuited * in the course of building the candidate set. * It may be {@code null} if there is no * remaining filter or if this information is * not available (e.g., because extended * response data was not requested). * @param debugInfo An optional list of messages providing debug * information about the processing performed by * the server. It may be {@code null} or empty * if no debug messages should be included. * * @return The matching entry count response control that was created. */ @NotNull() public static MatchingEntryCountResponseControl createExactCountResponse( final int count, final boolean examined, final boolean searchIndexed, @Nullable final Boolean shortCircuited, @Nullable final Boolean fullyIndexed, @Nullable final Boolean candidatesAreInScope, @Nullable final Filter remainingFilter, @Nullable final Collection debugInfo) { Validator.ensureTrue(count >= 0); final MatchingEntryCountType countType; if (examined) { countType = MatchingEntryCountType.EXAMINED_COUNT; } else { countType = MatchingEntryCountType.UNEXAMINED_COUNT; } return new MatchingEntryCountResponseControl(countType, count, searchIndexed, shortCircuited, fullyIndexed, candidatesAreInScope, remainingFilter, debugInfo); } /** * Creates a new matching entry count response control for the case in which * the exact number of matching entries is not known, but the server was able * to determine an upper bound on the number of matching entries. This upper * bound count may include entries that do not match the search filter, that * are outside the scope of the search, and/or that match the search criteria * but would not have been returned to the client in a normal search with the * same criteria. * * @param upperBound The upper bound on the number of entries that match the * associated search criteria. It must be greater than * zero. * @param debugInfo An optional list of messages providing debug * information about the processing performed by the * server. It may be {@code null} or empty if no debug * messages should be included. * * @return The matching entry count response control that was created. */ @NotNull() public static MatchingEntryCountResponseControl createUpperBoundResponse( final int upperBound, @Nullable final Collection debugInfo) { return createUpperBoundResponse(upperBound, true, debugInfo); } /** * Creates a new matching entry count response control for the case in which * the exact number of matching entries is not known, but the server was able * to determine an upper bound on the number of matching entries. This upper * bound count may include entries that do not match the search filter, that * are outside the scope of the search, and/or that match the search criteria * but would not have been returned to the client in a normal search with the * same criteria. * * @param upperBound The upper bound on the number of entries that match * the associated search criteria. It must be greater * than zero. * @param searchIndexed Indicates whether the search criteria is considered * at least partially indexed and could be processed * more efficiently than examining all entries with a * full database scan. * @param debugInfo An optional list of messages providing debug * information about the processing performed by the * server. It may be {@code null} or empty if no debug * messages should be included. * * @return The matching entry count response control that was created. */ @NotNull() public static MatchingEntryCountResponseControl createUpperBoundResponse( final int upperBound, final boolean searchIndexed, @Nullable final Collection debugInfo) { return createUpperBoundResponse(upperBound, searchIndexed, null, null, null, null, debugInfo); } /** * Creates a new matching entry count response control for the case in which * the exact number of matching entries is not known, but the server was able * to determine an upper bound on the number of matching entries. This upper * bound count may include entries that do not match the search filter, that * are outside the scope of the search, and/or that match the search criteria * but would not have been returned to the client in a normal search with the * same criteria. * * @param upperBound The upper bound on the number of entries that * match the associated search criteria. It * must be greater than zero. * @param searchIndexed Indicates whether the search criteria is * considered at least partially indexed and * could be processed more efficiently than * examining all entries with a full database * scan. * @param shortCircuited Indicates whether the server short-circuited * during candidate set processing before * evaluating all elements of the search * criteria (the filter and scope). This may be * {@code null} if it is not available (e.g., * because extended response data was not * requested). * @param fullyIndexed Indicates whether the search is considered * fully indexed. Note that this may be * {@code false} even if the filter is actually * fully indexed if server index processing * short-circuited before evaluating all * components of the filter. To avoid this, * issue the request control with both fast and * slow short-circuit thresholds set to zero. * This may be {@code null} if this is not * available (e.g., because extended response * data was not requested). * @param candidatesAreInScope Indicates whether all the identified * candidate entries are within the scope of * the search. It may be {@code null} if this * is not available (e.g., because extended * response data was not requested). * @param remainingFilter The portion of the filter that was either * identified as unindexed or that was not * evaluated because processing short-circuited * in the course of building the candidate set. * It may be {@code null} if there is no * remaining filter or if this information is * not available (e.g., because extended * response data was not requested). * @param debugInfo An optional list of messages providing debug * information about the processing performed by * the server. It may be {@code null} or empty * if no debug messages should be included. * * @return The matching entry count response control that was created. */ @NotNull() public static MatchingEntryCountResponseControl createUpperBoundResponse( final int upperBound, final boolean searchIndexed, @Nullable final Boolean shortCircuited, @Nullable final Boolean fullyIndexed, @Nullable final Boolean candidatesAreInScope, @Nullable final Filter remainingFilter, @Nullable final Collection debugInfo) { Validator.ensureTrue(upperBound > 0); return new MatchingEntryCountResponseControl( MatchingEntryCountType.UPPER_BOUND, upperBound, searchIndexed, shortCircuited, fullyIndexed, candidatesAreInScope, remainingFilter, debugInfo); } /** * Creates a new matching entry count response control for the case in which * the server was unable to make any meaningful determination about the number * of entries matching the search criteria. * * @param debugInfo An optional list of messages providing debug information * about the processing performed by the server. It may be * {@code null} or empty if no debug messages should be * included. * * @return The matching entry count response control that was created. */ @NotNull() public static MatchingEntryCountResponseControl createUnknownCountResponse( @Nullable final Collection debugInfo) { return new MatchingEntryCountResponseControl(MatchingEntryCountType.UNKNOWN, -1, false, null, null, null, null, debugInfo); } /** * Encodes a control value with the provided information. * * @param countType The matching entry count type. It must not * be {@code null}. * @param countValue The matching entry count value. It must be * greater than or equal to zero for a count * type of either {@code EXAMINED_COUNT} or * {@code UNEXAMINED_COUNT}. It must be greater * than zero for a count type of * {@code UPPER_BOUND}. It must be -1 for a * count type of {@code UNKNOWN}. * @param searchIndexed Indicates whether the search criteria is * considered at least partially indexed and * could be processed more efficiently than * examining all entries with a full database * scan. * @param shortCircuited Indicates whether the server short-circuited * during candidate set processing before * evaluating all elements of the search * criteria (the filter and scope). This may be * {@code null} if it is not available (e.g., * because extended response data was not * requested). * @param fullyIndexed Indicates whether the search is considered * fully indexed. Note that this may be * {@code false} even if the filter is actually * fully indexed if server index processing * short-circuited before evaluating all * components of the filter. To avoid this, * issue the request control with both fast and * slow short-circuit thresholds set to zero. * This may be {@code null} if this is not * available (e.g., because extended response * data was not requested). * @param candidatesAreInScope Indicates whether all the identified * candidate entries are within the scope of * the search. It may be {@code null} if this * is not available (e.g., because extended * response data was not requested). * @param remainingFilter The portion of the filter that was either * identified as unindexed or that was not * evaluated because processing short-circuited * in the course of building the candidate set. * It may be {@code null} if there is no * remaining filter or if this information is * not available (e.g., because extended * response data was not requested). * @param debugInfo An optional list of messages providing debug * information about the processing performed by * the server. It may be {@code null} or empty * if no debug messages should be included. * * @return The encoded control value. */ @NotNull() private static ASN1OctetString encodeValue( @NotNull final MatchingEntryCountType countType, final int countValue, final boolean searchIndexed, @Nullable final Boolean shortCircuited, @Nullable final Boolean fullyIndexed, @Nullable final Boolean candidatesAreInScope, @Nullable final Filter remainingFilter, @Nullable final Collection debugInfo) { final ArrayList elements = new ArrayList<>(3); switch (countType) { case EXAMINED_COUNT: case UNEXAMINED_COUNT: case UPPER_BOUND: elements.add(new ASN1Integer(countType.getBERType(), countValue)); break; case UNKNOWN: elements.add(new ASN1Null(countType.getBERType())); break; } if (debugInfo != null) { final ArrayList debugElements = new ArrayList<>(debugInfo.size()); for (final String s : debugInfo) { debugElements.add(new ASN1OctetString(s)); } elements.add(new ASN1Sequence(TYPE_DEBUG_INFO, debugElements)); } if (! searchIndexed) { elements.add(new ASN1Boolean(TYPE_SEARCH_INDEXED, searchIndexed)); } if (shortCircuited != null) { elements.add(new ASN1Boolean(TYPE_SHORT_CIRCUITED, shortCircuited)); } if (fullyIndexed != null) { elements.add(new ASN1Boolean(TYPE_FULLY_INDEXED, fullyIndexed)); } if (candidatesAreInScope != null) { elements.add(new ASN1Boolean(TYPE_CANDIDATES_ARE_IN_SCOPE, candidatesAreInScope)); } if (remainingFilter != null) { elements.add(new ASN1OctetString(TYPE_REMAINING_FILTER, remainingFilter.encode().encode())); } return new ASN1OctetString(new ASN1Sequence(elements).encode()); } /** * Retrieves the matching entry count type for the response control. * * @return The matching entry count type for the response control. */ @NotNull() public MatchingEntryCountType getCountType() { return countType; } /** * Retrieves the matching entry count value for the response control. For a * count type of {@code EXAMINED_COUNT} or {@code UNEXAMINED_COUNT}, this is * the exact number of matching entries. For a count type of * {@code UPPER_BOUND}, this is the maximum number of entries that may match * the search criteria, but it may also include entries that do not match the * criteria. For a count type of {@code UNKNOWN}, this will always be -1. * * @return The exact count or upper bound of the number of entries in the * server that may match the search criteria, or -1 if the server * could not determine the number of matching entries. */ public int getCountValue() { return countValue; } /** * Indicates whether the server considers the search criteria to be indexed * and therefore it could be processed more efficiently than examining all * entries with a full database scan. * * @return {@code true} if the server considers the search criteria to be * indexed, or {@code false} if not. */ public boolean searchIndexed() { return searchIndexed; } /** * Indicates whether the server short-circuited during candidate set * processing before evaluating all elements of the search criteria (the * filter and scope). * * @return {@code Boolean.TRUE} if the server did short-circuit during * candidate set processing before evaluating all elements of the * search criteria, {@code Boolean.FALSE} if the server evaluated all * elements of the search criteria, or {@code null} if this * information is not available (e.g., because extended response data * was not requested). */ @Nullable() public Boolean getShortCircuited() { return shortCircuited; } /** * Indicates whether the server considers the search criteria to be fully * indexed. Note that if the server short-circuited during candidate set * processing before evaluating all search criteria (the filter and scope), * this may be {@code Boolean.FALSE} even if the search is actually completely * indexed. * * @return {@code Boolean.TRUE} if the server considers the search criteria * to be fully indexed, {@code Boolean.FALSE} if the search criteria * is not known to be fully indexed, or {@code null} if this * information is not available (e.g., because extended response data * was not requested). */ @Nullable() public Boolean getFullyIndexed() { return fullyIndexed; } /** * Indicates whether the server can determine that all the identified * candidates are within the scope of the search. Note that even if the * server returns {@code Boolean.FALSE}, it does not necessarily mean that * not all the candidates are within the scope of the search, but just that * the server is not certain that is the case. * * @return {@code Boolean.TRUE} if the server can determine that all the * identified candidates are within the scope of the search, * {@code Boolean.FALSE} if the server cannot determine that all the * identified candidates are within the scope of the search, or * {@code null} if this information is not available (e.g., because * extended response data was not requested). */ @Nullable() public Boolean getCandidatesAreInScope() { return candidatesAreInScope; } /** * Retrieves the portion of the filter that was either identified as not * indexed or that was not evaluated during candidate processing (e.g., * because the server short-circuited processing before examining all filter * components). * * @return The portion of the filter that was either identified as not * indexed or that was not evaluated during candidate processing, or * {@code null} if there was no remaining filter or if this * information is not available (e.g., because extended response data * was not requested). */ @Nullable() public Filter getRemainingFilter() { return remainingFilter; } /** * Retrieves a list of messages with debug information about the processing * performed by the server in the course of obtaining the matching entry * count. These messages are intended to be human-readable rather than * machine-parsable. * * @return A list of messages with debug information about the processing * performed by the server in the course of obtaining the matching * entry count, or an empty list if no debug messages were provided. */ @NotNull() public List getDebugInfo() { return debugInfo; } /** * {@inheritDoc} */ @Override() @NotNull() public MatchingEntryCountResponseControl decodeControl( @NotNull final String oid, final boolean isCritical, @Nullable final ASN1OctetString value) throws LDAPException { return new MatchingEntryCountResponseControl(oid, isCritical, value); } /** * Extracts a matching entry count response control from the provided search * result. * * @param result The search result from which to retrieve the matching entry * count response control. * * @return The matching entry count response control contained in the * provided result, or {@code null} if the result did not contain a * matching entry count response control. * * @throws LDAPException If a problem is encountered while attempting to * decode the matching entry count response control * contained in the provided result. */ @Nullable() public static MatchingEntryCountResponseControl get( @NotNull final SearchResult result) throws LDAPException { final Control c = result.getResponseControl(MATCHING_ENTRY_COUNT_RESPONSE_OID); if (c == null) { return null; } if (c instanceof MatchingEntryCountResponseControl) { return (MatchingEntryCountResponseControl) c; } else { return new MatchingEntryCountResponseControl(c.getOID(), c.isCritical(), c.getValue()); } } /** * {@inheritDoc} */ @Override() @NotNull() public String getControlName() { return INFO_CONTROL_NAME_MATCHING_ENTRY_COUNT_RESPONSE.get(); } /** * Retrieves a representation of this matching entry count response control as * a JSON object. The JSON object uses the following fields: *
    *
  • * {@code oid} -- A mandatory string field whose value is the object * identifier for this control. For the matching entry count response * control, the OID is "1.3.6.1.4.1.30221.2.5.37". *
  • *
  • * {@code control-name} -- An optional string field whose value is a * human-readable name for this control. This field is only intended for * descriptive purposes, and when decoding a control, the {@code oid} * field should be used to identify the type of control. *
  • *
  • * {@code criticality} -- A mandatory Boolean field used to indicate * whether this control is considered critical. *
  • *
  • * {@code value-base64} -- An optional string field whose value is a * base64-encoded representation of the raw value for this matching entry * count response control. Exactly one of the {@code value-base64} and * {@code value-json} fields must be present. *
  • *
  • * {@code value-json} -- An optional JSON object field whose value is a * user-friendly representation of the value for this matching entry count * response control. Exactly one of the {@code value-base64} and * {@code value-json} fields must be present, and if the * {@code value-json} field is used, then it will use the following * fields: *
      *
    • * {@code count-type} -- A string field whose value indicates how * accurate the count is. The value will be one of * "{@code examined-count}", "{@code unexamined-count}", * "{@code upper-bound}", or "{@code unknown}". *
    • *
    • * {@code count-value} -- An optional integer field whose value is the * matching entry count estimate returned by the server. This will * be absent for a {@code count-type} value of "{@code unknown}", and * will be present for other {@code count-type} values. *
    • *
    • * {@code search-indexed} -- A Boolean field that indicates whether * the server considers the search to be at least partially indexed. *
    • *
    • * {@code fully-indexed} -- An optional Boolean field that indicates * whether the server considers the search to be fully indexed. *
    • *
    • * {@code short-circuited} -- An optional Boolean field that indicates * whether the server short-circuited at any point in evaluating the * search criteria. *
    • *
    • * {@code candidates-are-in-scope} -- An optional Boolean field that * indicates whether the server knows that all identified candidate * entries are within the scope of the search. *
    • *
    • * {@code remaining-filter} -- An optional string field whose value is * the portion of the filter that was not evaluated during the course * of coming up with the estimate. *
    • *
    • * {@code debug-info} -- An optional array field whose values are * strings with debug information about the processing performed by * the server in the course of determining the matching entry count * estimate. *
    • *
    *
  • *
* * @return A JSON object that contains a representation of this control. */ @Override() @NotNull() public JSONObject toJSONControl() { final Map valueFields = new LinkedHashMap<>(); switch (countType) { case EXAMINED_COUNT: valueFields.put(JSON_FIELD_COUNT_TYPE, new JSONString(JSON_COUNT_TYPE_EXAMINED_COUNT)); valueFields.put(JSON_FIELD_COUNT_VALUE, new JSONNumber(countValue)); break; case UNEXAMINED_COUNT: valueFields.put(JSON_FIELD_COUNT_TYPE, new JSONString(JSON_COUNT_TYPE_UNEXAMINED_COUNT)); valueFields.put(JSON_FIELD_COUNT_VALUE, new JSONNumber(countValue)); break; case UPPER_BOUND: valueFields.put(JSON_FIELD_COUNT_TYPE, new JSONString(JSON_COUNT_TYPE_UPPER_BOUND)); valueFields.put(JSON_FIELD_COUNT_VALUE, new JSONNumber(countValue)); break; case UNKNOWN: valueFields.put(JSON_FIELD_COUNT_TYPE, new JSONString(JSON_COUNT_TYPE_UNKNOWN)); break; } valueFields.put(JSON_FIELD_SEARCH_INDEXED, new JSONBoolean(searchIndexed)); if (fullyIndexed != null) { valueFields.put(JSON_FIELD_FULLY_INDEXED, new JSONBoolean(fullyIndexed)); } if (shortCircuited != null) { valueFields.put(JSON_FIELD_SHORT_CIRCUITED, new JSONBoolean(shortCircuited)); } if (candidatesAreInScope != null) { valueFields.put(JSON_FIELD_CANDIDATES_ARE_IN_SCOPE, new JSONBoolean(candidatesAreInScope)); } if (remainingFilter != null) { valueFields.put(JSON_FIELD_REMAINING_FILTER, new JSONString(remainingFilter.toString())); } if ((debugInfo != null) && (! debugInfo.isEmpty())) { final List debugInfoValues = new ArrayList<>(debugInfo.size()); for (final String s : debugInfo) { debugInfoValues.add(new JSONString(s)); } valueFields.put(JSON_FIELD_DEBUG_INFO, new JSONArray(debugInfoValues)); } return new JSONObject( new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, MATCHING_ENTRY_COUNT_RESPONSE_OID), new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, INFO_CONTROL_NAME_MATCHING_ENTRY_COUNT_RESPONSE.get()), new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, isCritical()), new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, new JSONObject(valueFields))); } /** * Attempts to decode the provided object as a JSON representation of a * matching entry count response control. * * @param controlObject The JSON object to be decoded. It must not be * {@code null}. * @param strict Indicates whether to use strict mode when decoding * the provided JSON object. If this is {@code true}, * then this method will throw an exception if the * provided JSON object contains any unrecognized * fields. If this is {@code false}, then unrecognized * fields will be ignored. * * @return The matching entry count response control that was decoded from * the provided JSON object. * * @throws LDAPException If the provided JSON object cannot be parsed as a * valid matching entry count response control. */ @NotNull() public static MatchingEntryCountResponseControl decodeJSONControl( @NotNull final JSONObject controlObject, final boolean strict) throws LDAPException { final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( controlObject, strict, true, true); final ASN1OctetString rawValue = jsonControl.getRawValue(); if (rawValue != null) { return new MatchingEntryCountResponseControl(jsonControl.getOID(), jsonControl.getCriticality(), rawValue); } final JSONObject valueObject = jsonControl.getValueObject(); final MatchingEntryCountType countType; final String countTypeStr = valueObject.getFieldAsString(JSON_FIELD_COUNT_TYPE); Integer countValue = valueObject.getFieldAsInteger(JSON_FIELD_COUNT_VALUE); if (countTypeStr == null) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_FIELD.get( controlObject.toSingleLineString(), JSON_FIELD_COUNT_TYPE)); } switch (countTypeStr) { case JSON_COUNT_TYPE_EXAMINED_COUNT: countType = MatchingEntryCountType.EXAMINED_COUNT; if (countValue == null) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_COUNT_VALUE.get( controlObject.toSingleLineString(), JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE, countTypeStr)); } break; case JSON_COUNT_TYPE_UNEXAMINED_COUNT: countType = MatchingEntryCountType.UNEXAMINED_COUNT; if (countValue == null) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_COUNT_VALUE.get( controlObject.toSingleLineString(), JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE, countTypeStr)); } break; case JSON_COUNT_TYPE_UPPER_BOUND: countType = MatchingEntryCountType.UPPER_BOUND; if (countValue == null) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_COUNT_VALUE.get( controlObject.toSingleLineString(), JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE, countTypeStr)); } break; case JSON_COUNT_TYPE_UNKNOWN: countType = MatchingEntryCountType.UNKNOWN; if (countValue == null) { countValue = -1; } else { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_UNEXPECTED_COUNT_VALUE. get(controlObject.toSingleLineString(), JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE, countTypeStr)); } break; default: throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_UNKNOWN_COUNT_TYPE.get( controlObject.toSingleLineString(), JSON_FIELD_COUNT_TYPE, countTypeStr)); } final Boolean searchIndexed = valueObject.getFieldAsBoolean(JSON_FIELD_SEARCH_INDEXED); if (searchIndexed == null) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_FIELD.get( controlObject.toSingleLineString(), JSON_FIELD_SEARCH_INDEXED)); } final Boolean fullyIndexed = valueObject.getFieldAsBoolean(JSON_FIELD_FULLY_INDEXED); final Boolean shortCircuited = valueObject.getFieldAsBoolean(JSON_FIELD_SHORT_CIRCUITED); final Boolean candidatesAreInScope = valueObject.getFieldAsBoolean(JSON_FIELD_CANDIDATES_ARE_IN_SCOPE); final Filter remainingFilter; final String remainingFilterStr = valueObject.getFieldAsString(JSON_FIELD_REMAINING_FILTER); if (remainingFilterStr == null) { remainingFilter = null; } else { try { remainingFilter = Filter.create(remainingFilterStr); } catch (final LDAPException e) { Debug.debugException(e); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_INVALID_FILTER.get( controlObject.toSingleLineString(), JSON_FIELD_REMAINING_FILTER, remainingFilterStr), e); } } final List debugInfo; final List debugInfoValues = valueObject.getFieldAsArray(JSON_FIELD_DEBUG_INFO); if (debugInfoValues == null) { debugInfo = null; } else { debugInfo = new ArrayList<>(debugInfoValues.size()); for (final JSONValue v : debugInfoValues) { if (v instanceof JSONString) { debugInfo.add(((JSONString) v).stringValue()); } else { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_DEBUG_INFO_NOT_STRING.get( controlObject.toSingleLineString(), JSON_FIELD_DEBUG_INFO)); } } } if (strict) { final List unrecognizedFields = JSONControlDecodeHelper.getControlObjectUnexpectedFields( valueObject, JSON_FIELD_COUNT_TYPE, JSON_FIELD_COUNT_VALUE, JSON_FIELD_SEARCH_INDEXED, JSON_FIELD_FULLY_INDEXED, JSON_FIELD_SHORT_CIRCUITED, JSON_FIELD_CANDIDATES_ARE_IN_SCOPE, JSON_FIELD_REMAINING_FILTER, JSON_FIELD_DEBUG_INFO); if (! unrecognizedFields.isEmpty()) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_UNRECOGNIZED_FIELD.get( controlObject.toSingleLineString(), unrecognizedFields.get(0))); } } return new MatchingEntryCountResponseControl(countType, countValue, searchIndexed, shortCircuited, fullyIndexed, candidatesAreInScope, remainingFilter, debugInfo); } /** * {@inheritDoc} */ @Override() public void toString(@NotNull final StringBuilder buffer) { buffer.append("MatchingEntryCountResponseControl(countType='"); buffer.append(countType.name()); buffer.append('\''); switch (countType) { case EXAMINED_COUNT: case UNEXAMINED_COUNT: buffer.append(", count="); buffer.append(countValue); break; case UPPER_BOUND: buffer.append(", upperBound="); buffer.append(countValue); break; } buffer.append(", searchIndexed="); buffer.append(searchIndexed); if (shortCircuited != null) { buffer.append(", shortCircuited="); buffer.append(shortCircuited); } if (fullyIndexed != null) { buffer.append(", fullyIndexed="); buffer.append(fullyIndexed); } if (candidatesAreInScope != null) { buffer.append(", candidatesAreInScope="); buffer.append(candidatesAreInScope); } if (remainingFilter != null) { buffer.append(", remainingFilter='"); remainingFilter.toString(buffer); buffer.append('\''); } if (! debugInfo.isEmpty()) { buffer.append(", debugInfo={"); final Iterator iterator = debugInfo.iterator(); while (iterator.hasNext()) { buffer.append('\''); buffer.append(iterator.next()); buffer.append('\''); if (iterator.hasNext()) { buffer.append(", "); } } buffer.append('}'); } buffer.append(')'); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy