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}