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