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 }