001/*
002 * Copyright 2008-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-2022 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2008-2022 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.controls;
037
038
039
040import java.util.List;
041
042import com.unboundid.asn1.ASN1Element;
043import com.unboundid.asn1.ASN1OctetString;
044import com.unboundid.asn1.ASN1Sequence;
045import com.unboundid.ldap.sdk.Control;
046import com.unboundid.ldap.sdk.LDAPException;
047import com.unboundid.ldap.sdk.ResultCode;
048import com.unboundid.util.Debug;
049import com.unboundid.util.NotMutable;
050import com.unboundid.util.NotNull;
051import com.unboundid.util.ThreadSafety;
052import com.unboundid.util.ThreadSafetyLevel;
053import com.unboundid.util.Validator;
054
055import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
056
057
058
059/**
060 * This class provides an implementation of the matched values request control
061 * as defined in <A HREF="http://www.ietf.org/rfc/rfc3876.txt">RFC 3876</A>.  It
062 * should only be used with a search request, in which case it indicates that
063 * only attribute values matching at least one of the provided
064 * {@link MatchedValuesFilter}s should be included in matching entries.  That
065 * is, this control may be used to restrict the set of values included in the
066 * entries that are returned.  This is particularly useful for multivalued
067 * attributes with a large number of values when only a small number of values
068 * are of interest to the client.
069 * <BR><BR>
070 * There are no corresponding response controls included in the search result
071 * entry, search result reference, or search result done messages returned for
072 * the associated search request.
073 * <BR><BR>
074 * <H2>Example</H2>
075 * The following example demonstrates the use of the matched values request
076 * control.  It will cause only values of the "{@code description}" attribute
077 * to be returned in which those values start with the letter f:
078 * <PRE>
079 * // Ensure that a test user has multiple description values.
080 * LDAPResult modifyResult = connection.modify(
081 *      "uid=test.user,ou=People,dc=example,dc=com",
082 *      new Modification(ModificationType.REPLACE,
083 *           "description", // Attribute name
084 *           "first", "second", "third", "fourth")); // Attribute values.
085 * assertResultCodeEquals(modifyResult, ResultCode.SUCCESS);
086 *
087 * // Perform a search to retrieve the test user entry without using the
088 * // matched values request control.  This should return all four description
089 * // values.
090 * SearchRequest searchRequest = new SearchRequest(
091 *      "uid=test.user,ou=People,dc=example,dc=com", // Base DN
092 *      SearchScope.BASE, // Scope
093 *      Filter.createPresenceFilter("objectClass"), // Filter
094 *      "description"); // Attributes to return.
095 * SearchResultEntry entryRetrievedWithoutControl =
096 *      connection.searchForEntry(searchRequest);
097 * Attribute fullDescriptionAttribute =
098 *      entryRetrievedWithoutControl.getAttribute("description");
099 * int numFullDescriptionValues = fullDescriptionAttribute.size();
100 *
101 * // Update the search request to include a matched values control that will
102 * // only return values that start with the letter "f".  In our test entry,
103 * // this should just match two values ("first" and "fourth").
104 * searchRequest.addControl(new MatchedValuesRequestControl(
105 *      MatchedValuesFilter.createSubstringFilter("description", // Attribute
106 *           "f", // subInitial component
107 *           null, // subAny components
108 *           null))); // subFinal component
109 * SearchResultEntry entryRetrievedWithControl =
110 *      connection.searchForEntry(searchRequest);
111 * Attribute partialDescriptionAttribute =
112 *      entryRetrievedWithControl.getAttribute("description");
113 * int numPartialDescriptionValues = partialDescriptionAttribute.size();
114 * </PRE>
115 */
116@NotMutable()
117@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
118public final class MatchedValuesRequestControl
119       extends Control
120{
121  /**
122   * The OID (1.2.826.0.1.3344810.2.3) for the matched values request control.
123   */
124  @NotNull public static final String MATCHED_VALUES_REQUEST_OID =
125       "1.2.826.0.1.3344810.2.3";
126
127
128
129  /**
130   * The serial version UID for this serializable class.
131   */
132  private static final long serialVersionUID = 6799850686547208774L;
133
134
135
136  // The set of matched values filters for this control.
137  @NotNull private final MatchedValuesFilter[] filters;
138
139
140
141  /**
142   * Creates a new matched values request control with the provided set of
143   * filters.  It will not be be marked as critical.
144   *
145   * @param  filters  The set of filters to use for this control.  At least one
146   *                  filter must be provided.
147   */
148  public MatchedValuesRequestControl(
149              @NotNull final MatchedValuesFilter... filters)
150  {
151    this(false, filters);
152  }
153
154
155
156  /**
157   * Creates a new matched values request control with the provided set of
158   * filters.  It will not be be marked as critical.
159   *
160   * @param  filters  The set of filters to use for this control.  At least one
161   *                  filter must be provided.
162   */
163  public MatchedValuesRequestControl(
164              @NotNull final List<MatchedValuesFilter> filters)
165  {
166    this(false, filters);
167  }
168
169
170
171  /**
172   * Creates a new matched values request control with the provided criticality
173   * and set of filters.
174   *
175   * @param  isCritical  Indicates whether this control should be marked
176   *                     critical.
177   * @param  filters     The set of filters to use for this control.  At least
178   *                     one filter must be provided.
179   */
180  public MatchedValuesRequestControl(final boolean isCritical,
181              @NotNull final MatchedValuesFilter... filters)
182  {
183    super(MATCHED_VALUES_REQUEST_OID, isCritical,  encodeValue(filters));
184
185    this.filters = filters;
186  }
187
188
189
190  /**
191   * Creates a new matched values request control with the provided criticality
192   * and set of filters.
193   *
194   * @param  isCritical  Indicates whether this control should be marked
195   *                     critical.
196   * @param  filters     The set of filters to use for this control.  At least
197   *                     one filter must be provided.
198   */
199  public MatchedValuesRequestControl(final boolean isCritical,
200              @NotNull final List<MatchedValuesFilter> filters)
201  {
202    this(isCritical, filters.toArray(new MatchedValuesFilter[filters.size()]));
203  }
204
205
206
207  /**
208   * Creates a new matched values request control which is decoded from the
209   * provided generic control.
210   *
211   * @param  control  The generic control to be decoded as a matched values
212   *                  request control.
213   *
214   * @throws  LDAPException  If the provided control cannot be decoded as a
215   *                         matched values request control.
216   */
217  public MatchedValuesRequestControl(@NotNull final Control control)
218         throws LDAPException
219  {
220    super(control);
221
222    final ASN1OctetString value = control.getValue();
223    if (value == null)
224    {
225      throw new LDAPException(ResultCode.DECODING_ERROR,
226                              ERR_MV_REQUEST_NO_VALUE.get());
227    }
228
229    try
230    {
231      final ASN1Element valueElement = ASN1Element.decode(value.getValue());
232      final ASN1Element[] filterElements =
233           ASN1Sequence.decodeAsSequence(valueElement).elements();
234      filters = new MatchedValuesFilter[filterElements.length];
235      for (int i=0; i < filterElements.length; i++)
236      {
237        filters[i] = MatchedValuesFilter.decode(filterElements[i]);
238      }
239    }
240    catch (final Exception e)
241    {
242      Debug.debugException(e);
243      throw new LDAPException(ResultCode.DECODING_ERROR,
244                              ERR_MV_REQUEST_CANNOT_DECODE.get(e), e);
245    }
246  }
247
248
249
250  /**
251   * Encodes the provided set of filters into a value appropriate for use with
252   * the matched values control.
253   *
254   * @param  filters  The set of filters to include in the value.  It must not
255   *                  be {@code null} or empty.
256   *
257   * @return  The ASN.1 octet string containing the encoded control value.
258   */
259  @NotNull()
260  private static ASN1OctetString encodeValue(
261                      @NotNull final MatchedValuesFilter[] filters)
262  {
263    Validator.ensureNotNull(filters);
264    Validator.ensureTrue(filters.length > 0,
265         "MatchedValuesRequestControl.filters must not be empty.");
266
267    final ASN1Element[] elements = new ASN1Element[filters.length];
268    for (int i=0; i < filters.length; i++)
269    {
270      elements[i] = filters[i].encode();
271    }
272
273    return new ASN1OctetString(new ASN1Sequence(elements).encode());
274  }
275
276
277
278  /**
279   * Retrieves the set of filters for this matched values request control.
280   *
281   * @return  The set of filters for this matched values request control.
282   */
283  @NotNull()
284  public MatchedValuesFilter[] getFilters()
285  {
286    return filters;
287  }
288
289
290
291  /**
292   * {@inheritDoc}
293   */
294  @Override()
295  @NotNull()
296  public String getControlName()
297  {
298    return INFO_CONTROL_NAME_MATCHED_VALUES_REQUEST.get();
299  }
300
301
302
303  /**
304   * {@inheritDoc}
305   */
306  @Override()
307  public void toString(@NotNull final StringBuilder buffer)
308  {
309    buffer.append("MatchedValuesRequestControl(filters={");
310
311    for (int i=0; i < filters.length; i++)
312    {
313      if (i > 0)
314      {
315        buffer.append(", ");
316      }
317
318      buffer.append('\'');
319      filters[i].toString(buffer);
320      buffer.append('\'');
321    }
322
323    buffer.append("}, isCritical=");
324    buffer.append(isCritical());
325    buffer.append(')');
326  }
327}