001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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}