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    }