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