001    /*
002     * Copyright 2007-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2013 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     *   try
075     *   {
076     *     LDAPResult modifyResult = connection.modify(modifyRequest);
077     *     // If we've gotten here, then the modification was successful.
078     *   }
079     *   catch (LDAPException le)
080     *   {
081     *     if (le.getResultCode() == ResultCode.ASSERTION_FAILED)
082     *     {
083     *       The modification failed because the accountBalance value wasn't what
084     *       we thought it was.
085     *     }
086     *     else
087     *     {
088     *       The modification failed for some other reason.
089     *     }
090     *   }
091     * </PRE>
092     */
093    @NotMutable()
094    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
095    public final class AssertionRequestControl
096           extends Control
097    {
098      /**
099       * The OID (1.3.6.1.1.12) for the assertion request control.
100       */
101      public static final String ASSERTION_REQUEST_OID = "1.3.6.1.1.12";
102    
103    
104    
105      /**
106       * The serial version UID for this serializable class.
107       */
108      private static final long serialVersionUID = 6592634203410511095L;
109    
110    
111    
112      // The search filter for this assertion request control.
113      private final Filter filter;
114    
115    
116    
117      /**
118       * Creates a new assertion request control with the provided filter.  It will
119       * be marked as critical.
120       *
121       * @param  filter  The string representation of the filter for this assertion
122       *                 control.  It must not be {@code null}.
123       *
124       * @throws  LDAPException  If the provided filter string cannot be decoded as
125       *                         a search filter.
126       */
127      public AssertionRequestControl(final String filter)
128             throws LDAPException
129      {
130        this(Filter.create(filter), true);
131      }
132    
133    
134    
135      /**
136       * Creates a new assertion request control with the provided filter.  It will
137       * be marked as critical.
138       *
139       * @param  filter  The filter for this assertion control.  It must not be
140       *                 {@code null}.
141       */
142      public AssertionRequestControl(final Filter filter)
143      {
144        this(filter, true);
145      }
146    
147    
148    
149      /**
150       * Creates a new assertion request control with the provided filter.  It will
151       * be marked as critical.
152       *
153       * @param  filter      The string representation of the filter for this
154       *                     assertion control.  It must not be {@code null}.
155       * @param  isCritical  Indicates whether this control should be marked
156       *                     critical.
157       *
158       * @throws  LDAPException  If the provided filter string cannot be decoded as
159       *                         a search filter.
160       */
161      public AssertionRequestControl(final String filter, final boolean isCritical)
162             throws LDAPException
163      {
164        this(Filter.create(filter), isCritical);
165      }
166    
167    
168    
169      /**
170       * Creates a new assertion request control with the provided filter.  It will
171       * be marked as critical.
172       *
173       * @param  filter      The filter for this assertion control.  It must not be
174       *                     {@code null}.
175       * @param  isCritical  Indicates whether this control should be marked
176       *                     critical.
177       */
178      public AssertionRequestControl(final Filter filter, final boolean isCritical)
179      {
180        super(ASSERTION_REQUEST_OID, isCritical, encodeValue(filter));
181    
182        this.filter = filter;
183      }
184    
185    
186    
187      /**
188       * Creates a new assertion request control which is decoded from the provided
189       * generic control.
190       *
191       * @param  control  The generic control to be decoded as an assertion request
192       *                  control.
193       *
194       * @throws  LDAPException  If the provided control cannot be decoded as an
195       *                         assertion request control.
196       */
197      public AssertionRequestControl(final Control control)
198             throws LDAPException
199      {
200        super(control);
201    
202        final ASN1OctetString value = control.getValue();
203        if (value == null)
204        {
205          throw new LDAPException(ResultCode.DECODING_ERROR,
206                                  ERR_ASSERT_NO_VALUE.get());
207        }
208    
209    
210        try
211        {
212          final ASN1Element valueElement = ASN1Element.decode(value.getValue());
213          filter = Filter.decode(valueElement);
214        }
215        catch (Exception e)
216        {
217          debugException(e);
218          throw new LDAPException(ResultCode.DECODING_ERROR,
219                                  ERR_ASSERT_CANNOT_DECODE.get(e), e);
220        }
221      }
222    
223    
224    
225      /**
226       * Generates an assertion request control that may be used to help ensure
227       * that some or all of the attributes in the specified entry have not changed
228       * since it was read from the server.
229       *
230       * @param  sourceEntry  The entry from which to take the attributes to include
231       *                      in the assertion request control.  It must not be
232       *                      {@code null} and should have at least one attribute to
233       *                      be included in the generated filter.
234       * @param  attributes   The names of the attributes to include in the
235       *                      assertion request control.  If this is empty or
236       *                      {@code null}, then all attributes in the provided
237       *                      entry will be used.
238       *
239       * @return  The generated assertion request control.
240       */
241      public static AssertionRequestControl generate(final Entry sourceEntry,
242                                                     final String... attributes)
243      {
244        Validator.ensureNotNull(sourceEntry);
245    
246        final ArrayList<Filter> andComponents;
247    
248        if ((attributes == null) || (attributes.length == 0))
249        {
250          final Collection<Attribute> entryAttrs = sourceEntry.getAttributes();
251          andComponents = new ArrayList<Filter>(entryAttrs.size());
252          for (final Attribute a : entryAttrs)
253          {
254            for (final ASN1OctetString v : a.getRawValues())
255            {
256              andComponents.add(Filter.createEqualityFilter(a.getName(),
257                   v.getValue()));
258            }
259          }
260        }
261        else
262        {
263          andComponents = new ArrayList<Filter>(attributes.length);
264          for (final String name : attributes)
265          {
266            final Attribute a = sourceEntry.getAttribute(name);
267            if (a != null)
268            {
269              for (final ASN1OctetString v : a.getRawValues())
270              {
271                andComponents.add(Filter.createEqualityFilter(name, v.getValue()));
272              }
273            }
274          }
275        }
276    
277        if (andComponents.size() == 1)
278        {
279          return new AssertionRequestControl(andComponents.get(0));
280        }
281        else
282        {
283          return new AssertionRequestControl(Filter.createANDFilter(andComponents));
284        }
285      }
286    
287    
288    
289      /**
290       * Encodes the provided information into an octet string that can be used as
291       * the value for this control.
292       *
293       * @param  filter  The filter for this assertion control.  It must not be
294       *                 {@code null}.
295       *
296       * @return  An ASN.1 octet string that can be used as the value for this
297       *          control.
298       */
299      private static ASN1OctetString encodeValue(final Filter filter)
300      {
301        return new ASN1OctetString(filter.encode().encode());
302      }
303    
304    
305    
306      /**
307       * Retrieves the filter for this assertion control.
308       *
309       * @return  The filter for this assertion control.
310       */
311      public Filter getFilter()
312      {
313        return filter;
314      }
315    
316    
317    
318      /**
319       * {@inheritDoc}
320       */
321      @Override()
322      public String getControlName()
323      {
324        return INFO_CONTROL_NAME_ASSERTION_REQUEST.get();
325      }
326    
327    
328    
329      /**
330       * {@inheritDoc}
331       */
332      @Override()
333      public void toString(final StringBuilder buffer)
334      {
335        buffer.append("AssertionRequestControl(filter='");
336        filter.toString(buffer);
337        buffer.append("', isCritical=");
338        buffer.append(isCritical());
339        buffer.append(')');
340      }
341    }