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     */
021    package com.unboundid.ldap.sdk.controls;
022    
023    
024    
025    import java.util.List;
026    
027    import com.unboundid.asn1.ASN1Element;
028    import com.unboundid.asn1.ASN1OctetString;
029    import com.unboundid.asn1.ASN1Sequence;
030    import com.unboundid.ldap.sdk.Control;
031    import com.unboundid.ldap.sdk.LDAPException;
032    import com.unboundid.ldap.sdk.ResultCode;
033    import com.unboundid.util.NotMutable;
034    import com.unboundid.util.ThreadSafety;
035    import com.unboundid.util.ThreadSafetyLevel;
036    
037    import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
038    import static com.unboundid.util.Debug.*;
039    import 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)
102    public 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    }