001/*
002 * Copyright 2007-2021 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2021 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-2021 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}