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