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}