001/* 002 * Copyright 2007-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2022 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2007-2022 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.controls; 037 038 039 040import java.util.ArrayList; 041import java.util.Collection; 042 043import com.unboundid.asn1.ASN1Element; 044import com.unboundid.asn1.ASN1OctetString; 045import com.unboundid.ldap.sdk.Attribute; 046import com.unboundid.ldap.sdk.Control; 047import com.unboundid.ldap.sdk.Entry; 048import com.unboundid.ldap.sdk.Filter; 049import com.unboundid.ldap.sdk.LDAPException; 050import com.unboundid.ldap.sdk.ResultCode; 051import com.unboundid.util.Debug; 052import com.unboundid.util.NotMutable; 053import com.unboundid.util.NotNull; 054import com.unboundid.util.Nullable; 055import com.unboundid.util.ThreadSafety; 056import com.unboundid.util.ThreadSafetyLevel; 057import com.unboundid.util.Validator; 058 059import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 060 061 062 063/** 064 * This class provides an implementation of the LDAP assertion request control 065 * as defined in <A HREF="http://www.ietf.org/rfc/rfc4528.txt">RFC 4528</A>. It 066 * may be used in conjunction with an add, compare, delete, modify, modify DN, 067 * or search operation. The assertion control includes a search filter, and the 068 * associated operation should only be allowed to continue if the target entry 069 * matches the provided filter. If the filter does not match the target entry, 070 * then the operation should fail with an 071 * {@link ResultCode#ASSERTION_FAILED} result. 072 * <BR><BR> 073 * The behavior of the assertion request control makes it ideal for atomic 074 * "check and set" types of operations, particularly when modifying an entry. 075 * For example, it can be used to ensure that when changing the value of an 076 * attribute, the current value has not been modified since it was last 077 * retrieved. 078 * <BR><BR> 079 * <H2>Example</H2> 080 * The following example demonstrates the use of the assertion request control. 081 * It shows an attempt to modify an entry's "accountBalance" attribute to set 082 * the value to "543.21" only if the current value is "1234.56": 083 * <PRE> 084 * Modification mod = new Modification(ModificationType.REPLACE, 085 * "accountBalance", "543.21"); 086 * ModifyRequest modifyRequest = 087 * new ModifyRequest("uid=john.doe,ou=People,dc=example,dc=com", mod); 088 * modifyRequest.addControl( 089 * new AssertionRequestControl("(accountBalance=1234.56)")); 090 * 091 * LDAPResult modifyResult; 092 * try 093 * { 094 * modifyResult = connection.modify(modifyRequest); 095 * // If we've gotten here, then the modification was successful. 096 * } 097 * catch (LDAPException le) 098 * { 099 * modifyResult = le.toLDAPResult(); 100 * ResultCode resultCode = le.getResultCode(); 101 * String errorMessageFromServer = le.getDiagnosticMessage(); 102 * if (resultCode == ResultCode.ASSERTION_FAILED) 103 * { 104 * // The modification failed because the account balance value wasn't 105 * // what we thought it was. 106 * } 107 * else 108 * { 109 * // The modification failed for some other reason. 110 * } 111 * } 112 * </PRE> 113 */ 114@NotMutable() 115@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 116public final class AssertionRequestControl 117 extends Control 118{ 119 /** 120 * The OID (1.3.6.1.1.12) for the assertion request control. 121 */ 122 @NotNull public static final String ASSERTION_REQUEST_OID = "1.3.6.1.1.12"; 123 124 125 126 /** 127 * The serial version UID for this serializable class. 128 */ 129 private static final long serialVersionUID = 6592634203410511095L; 130 131 132 133 // The search filter for this assertion request control. 134 @NotNull private final Filter filter; 135 136 137 138 /** 139 * Creates a new assertion request control with the provided filter. It will 140 * be marked as critical. 141 * 142 * @param filter The string representation of the filter for this assertion 143 * control. It must not be {@code null}. 144 * 145 * @throws LDAPException If the provided filter string cannot be decoded as 146 * a search filter. 147 */ 148 public AssertionRequestControl(@NotNull final String filter) 149 throws LDAPException 150 { 151 this(Filter.create(filter), true); 152 } 153 154 155 156 /** 157 * Creates a new assertion request control with the provided filter. It will 158 * be marked as critical. 159 * 160 * @param filter The filter for this assertion control. It must not be 161 * {@code null}. 162 */ 163 public AssertionRequestControl(@NotNull final Filter filter) 164 { 165 this(filter, true); 166 } 167 168 169 170 /** 171 * Creates a new assertion request control with the provided filter. It will 172 * be marked as critical. 173 * 174 * @param filter The string representation of the filter for this 175 * assertion control. It must not be {@code null}. 176 * @param isCritical Indicates whether this control should be marked 177 * critical. 178 * 179 * @throws LDAPException If the provided filter string cannot be decoded as 180 * a search filter. 181 */ 182 public AssertionRequestControl(@NotNull final String filter, 183 final boolean isCritical) 184 throws LDAPException 185 { 186 this(Filter.create(filter), isCritical); 187 } 188 189 190 191 /** 192 * Creates a new assertion request control with the provided filter. It will 193 * be marked as critical. 194 * 195 * @param filter The filter for this assertion control. It must not be 196 * {@code null}. 197 * @param isCritical Indicates whether this control should be marked 198 * critical. 199 */ 200 public AssertionRequestControl(@NotNull final Filter filter, 201 final boolean isCritical) 202 { 203 super(ASSERTION_REQUEST_OID, isCritical, encodeValue(filter)); 204 205 this.filter = filter; 206 } 207 208 209 210 /** 211 * Creates a new assertion request control which is decoded from the provided 212 * generic control. 213 * 214 * @param control The generic control to be decoded as an assertion request 215 * control. 216 * 217 * @throws LDAPException If the provided control cannot be decoded as an 218 * assertion request control. 219 */ 220 public AssertionRequestControl(@NotNull final Control control) 221 throws LDAPException 222 { 223 super(control); 224 225 final ASN1OctetString value = control.getValue(); 226 if (value == null) 227 { 228 throw new LDAPException(ResultCode.DECODING_ERROR, 229 ERR_ASSERT_NO_VALUE.get()); 230 } 231 232 233 try 234 { 235 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 236 filter = Filter.decode(valueElement); 237 } 238 catch (final Exception e) 239 { 240 Debug.debugException(e); 241 throw new LDAPException(ResultCode.DECODING_ERROR, 242 ERR_ASSERT_CANNOT_DECODE.get(e), e); 243 } 244 } 245 246 247 248 /** 249 * Generates an assertion request control that may be used to help ensure 250 * that some or all of the attributes in the specified entry have not changed 251 * since it was read from the server. 252 * 253 * @param sourceEntry The entry from which to take the attributes to include 254 * in the assertion request control. It must not be 255 * {@code null} and should have at least one attribute to 256 * be included in the generated filter. 257 * @param attributes The names of the attributes to include in the 258 * assertion request control. If this is empty or 259 * {@code null}, then all attributes in the provided 260 * entry will be used. 261 * 262 * @return The generated assertion request control. 263 */ 264 @NotNull() 265 public static AssertionRequestControl generate( 266 @NotNull final Entry sourceEntry, 267 @Nullable final String... attributes) 268 { 269 Validator.ensureNotNull(sourceEntry); 270 271 final ArrayList<Filter> andComponents; 272 273 if ((attributes == null) || (attributes.length == 0)) 274 { 275 final Collection<Attribute> entryAttrs = sourceEntry.getAttributes(); 276 andComponents = new ArrayList<>(entryAttrs.size()); 277 for (final Attribute a : entryAttrs) 278 { 279 for (final ASN1OctetString v : a.getRawValues()) 280 { 281 andComponents.add(Filter.createEqualityFilter(a.getName(), 282 v.getValue())); 283 } 284 } 285 } 286 else 287 { 288 andComponents = new ArrayList<>(attributes.length); 289 for (final String name : attributes) 290 { 291 final Attribute a = sourceEntry.getAttribute(name); 292 if (a != null) 293 { 294 for (final ASN1OctetString v : a.getRawValues()) 295 { 296 andComponents.add(Filter.createEqualityFilter(name, v.getValue())); 297 } 298 } 299 } 300 } 301 302 if (andComponents.size() == 1) 303 { 304 return new AssertionRequestControl(andComponents.get(0)); 305 } 306 else 307 { 308 return new AssertionRequestControl(Filter.createANDFilter(andComponents)); 309 } 310 } 311 312 313 314 /** 315 * Encodes the provided information into an octet string that can be used as 316 * the value for this control. 317 * 318 * @param filter The filter for this assertion control. It must not be 319 * {@code null}. 320 * 321 * @return An ASN.1 octet string that can be used as the value for this 322 * control. 323 */ 324 @NotNull() 325 private static ASN1OctetString encodeValue(@NotNull final Filter filter) 326 { 327 return new ASN1OctetString(filter.encode().encode()); 328 } 329 330 331 332 /** 333 * Retrieves the filter for this assertion control. 334 * 335 * @return The filter for this assertion control. 336 */ 337 @NotNull() 338 public Filter getFilter() 339 { 340 return filter; 341 } 342 343 344 345 /** 346 * {@inheritDoc} 347 */ 348 @Override() 349 @NotNull() 350 public String getControlName() 351 { 352 return INFO_CONTROL_NAME_ASSERTION_REQUEST.get(); 353 } 354 355 356 357 /** 358 * {@inheritDoc} 359 */ 360 @Override() 361 public void toString(@NotNull final StringBuilder buffer) 362 { 363 buffer.append("AssertionRequestControl(filter='"); 364 filter.toString(buffer); 365 buffer.append("', isCritical="); 366 buffer.append(isCritical()); 367 buffer.append(')'); 368 } 369}