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