001 /*
002 * Copyright 2007-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 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 */
021 package com.unboundid.ldap.sdk.controls;
022
023
024
025 import java.util.ArrayList;
026 import java.util.Collection;
027
028 import com.unboundid.asn1.ASN1Element;
029 import com.unboundid.asn1.ASN1OctetString;
030 import com.unboundid.ldap.sdk.Attribute;
031 import com.unboundid.ldap.sdk.Control;
032 import com.unboundid.ldap.sdk.Entry;
033 import com.unboundid.ldap.sdk.Filter;
034 import com.unboundid.ldap.sdk.LDAPException;
035 import com.unboundid.ldap.sdk.ResultCode;
036 import com.unboundid.util.NotMutable;
037 import com.unboundid.util.ThreadSafety;
038 import com.unboundid.util.ThreadSafetyLevel;
039 import com.unboundid.util.Validator;
040
041 import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
042 import 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)
099 public 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 }