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