001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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 com.unboundid.asn1.ASN1Element;
026    import com.unboundid.asn1.ASN1OctetString;
027    import com.unboundid.asn1.ASN1Sequence;
028    import com.unboundid.ldap.sdk.Control;
029    import com.unboundid.ldap.sdk.LDAPException;
030    import com.unboundid.ldap.sdk.ResultCode;
031    import com.unboundid.util.NotMutable;
032    import com.unboundid.util.StaticUtils;
033    import com.unboundid.util.ThreadSafety;
034    import com.unboundid.util.ThreadSafetyLevel;
035    
036    import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
037    import static com.unboundid.util.Debug.*;
038    
039    
040    
041    /**
042     * This class provides an implementation of the LDAP post-read request control
043     * as defined in <A HREF="http://www.ietf.org/rfc/rfc4527.txt">RFC 4527</A>.  It
044     * may be used to request that the server retrieve a copy of the target entry as
045     * it appeared immediately after processing an add, modify, or modify DN
046     * operation.
047     * <BR><BR>
048     * If this control is included in an add, modify, or modify DN request, then the
049     * corresponding response may include a {@link PostReadResponseControl}
050     * containing a version of the entry as it appeared after applying that change.
051     * Note that this response control will only be included if the operation was
052     * successful, so it will not be provided if the operation failed for some
053     * reason (e.g., if the change would have violated the server schema, or if the
054     * requester did not have sufficient permission to perform that operation).
055     * <BR><BR>
056     * The value of this control should contain a set of requested attributes to
057     * include in the entry that is returned.  The server should treat this set of
058     * requested attributes exactly as it treats the requested attributes from a
059     * {@link com.unboundid.ldap.sdk.SearchRequest}.  As is the case with a search
060     * request, if no attributes are specified, then all user attributes will be
061     * included.
062     * <BR><BR>
063     * <H2>Example</H2>
064     * The following example demonstrates the use of the pre-read and post-read
065     * controls.  It will modify an entry to increment the value of the
066     * {@code test-counter} attribute by one, and will use the pre-read and
067     * post-read controls to determine what the previous and updated values are:
068     * <PRE>
069     * // Create a modify request that we can use to increment the value of a
070     * // custom attribute named "test-counter".
071     * ModifyRequest modifyRequest = new ModifyRequest(
072     *      "uid=test.user,ou=People,dc=example,dc=com",
073     *      new Modification(ModificationType.INCREMENT,
074     *           "test-counter", // The attribute to increment.
075     *           "1")); // The amount by which to increment the value.
076     *
077     * // Update the modify request to add both pre-read and post-read request
078     * // controls to see what the entry value was before and after the change.
079     * // We only care about getting the test-counter attribute.
080     * modifyRequest.setControls(
081     *      new PreReadRequestControl("test-counter"),
082     *      new PostReadRequestControl("test-counter"));
083     *
084     * // Process the modify operation in the server.
085     * LDAPResult modifyResult;
086     * try
087     * {
088     *   modifyResult = connection.modify(modifyRequest);
089     *   // If we got here, then the modification should have been successful.
090     * }
091     * catch (LDAPException le)
092     * {
093     *   // This indicates that the operation did not complete successfully.
094     *   modifyResult = le.toLDAPResult();
095     *   ResultCode resultCode = le.getResultCode();
096     *   String errorMessageFromServer = le.getDiagnosticMessage();
097     * }
098     * LDAPTestUtils.assertResultCodeEquals(modifyResult, ResultCode.SUCCESS);
099     *
100     * // Get the pre-read and post-read response controls from the server and
101     * // retrieve the before and after values for the test-counter attribute.
102     * LDAPTestUtils.assertHasControl(modifyResult,
103     *      PreReadResponseControl.PRE_READ_RESPONSE_OID);
104     * PreReadResponseControl preReadResponse =
105     *      PreReadResponseControl.get(modifyResult);
106     * Integer beforeValue =
107     *      preReadResponse.getEntry().getAttributeValueAsInteger("test-counter");
108     *
109     * LDAPTestUtils.assertHasControl(modifyResult,
110     *      PostReadResponseControl.POST_READ_RESPONSE_OID);
111     * PostReadResponseControl postReadResponse =
112     *      PostReadResponseControl.get(modifyResult);
113     * Integer afterValue =
114     *      postReadResponse.getEntry().getAttributeValueAsInteger("test-counter");
115     * </PRE>
116     */
117    @NotMutable()
118    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
119    public final class PostReadRequestControl
120           extends Control
121    {
122      /**
123       * The OID (1.3.6.1.1.13.2) for the post-read request control.
124       */
125      public static final String POST_READ_REQUEST_OID = "1.3.6.1.1.13.2";
126    
127    
128    
129      /**
130       * The set of requested attributes that will be used if none are provided.
131       */
132      private static final String[] NO_ATTRIBUTES = StaticUtils.NO_STRINGS;
133    
134    
135    
136      /**
137       * The serial version UID for this serializable class.
138       */
139      private static final long serialVersionUID = -4210061989410209462L;
140    
141    
142    
143      // The set of requested attributes to retrieve from the target entry.
144      private final String[] attributes;
145    
146    
147    
148      /**
149       * Creates a new post-read request control that will retrieve the specified
150       * set of attributes from the target entry.  It will be marked critical.
151       *
152       * @param  attributes  The set of attributes to retrieve from the target
153       *                     entry.  It behaves in the same way as the set of
154       *                     requested attributes for a search operation.  If this
155       *                     is empty or {@code null}, then all user attributes
156       *                     will be returned.
157       */
158      public PostReadRequestControl(final String... attributes)
159      {
160        this(true, attributes);
161      }
162    
163    
164    
165      /**
166       * Creates a new post-read request control that will retrieve the specified
167       * set of attributes from the target entry.
168       *
169       * @param  isCritical  Indicates whether this control should be marked
170       *                     critical.
171       * @param  attributes  The set of attributes to retrieve from the target
172       *                     entry.  It behaves in the same way as the set of
173       *                     requested attributes for a search operation.  If this
174       *                     is empty or {@code null}, then all user attributes
175       *                     will be returned.
176       */
177      public PostReadRequestControl(final boolean isCritical,
178                                    final String... attributes)
179      {
180        super(POST_READ_REQUEST_OID, isCritical, encodeValue(attributes));
181    
182        if (attributes == null)
183        {
184          this.attributes = NO_ATTRIBUTES;
185        }
186        else
187        {
188          this.attributes = attributes;
189        }
190      }
191    
192    
193    
194      /**
195       * Creates a new post-read request control which is decoded from the provided
196       * generic control.
197       *
198       * @param  control  The generic control to be decoded as a post-read request
199       *                  control.
200       *
201       * @throws  LDAPException  If the provided control cannot be decoded as a
202       *                         post-read request control.
203       */
204      public PostReadRequestControl(final Control control)
205             throws LDAPException
206      {
207        super(control);
208    
209        final ASN1OctetString value = control.getValue();
210        if (value == null)
211        {
212          throw new LDAPException(ResultCode.DECODING_ERROR,
213                                  ERR_POST_READ_REQUEST_NO_VALUE.get());
214        }
215    
216        try
217        {
218          final ASN1Element valueElement = ASN1Element.decode(value.getValue());
219          final ASN1Element[] attrElements =
220               ASN1Sequence.decodeAsSequence(valueElement).elements();
221          attributes = new String[attrElements.length];
222          for (int i=0; i < attrElements.length; i++)
223          {
224            attributes[i] =
225                 ASN1OctetString.decodeAsOctetString(attrElements[i]).stringValue();
226          }
227        }
228        catch (Exception e)
229        {
230          debugException(e);
231          throw new LDAPException(ResultCode.DECODING_ERROR,
232                                  ERR_POST_READ_REQUEST_CANNOT_DECODE.get(e), e);
233        }
234      }
235    
236    
237    
238      /**
239       * Encodes the provided information into an octet string that can be used as
240       * the value for this control.
241       *
242       * @param  attributes  The set of attributes to retrieve from the target
243       *                     entry.  It behaves in the same way as the set of
244       *                     requested attributes for a search operation.  If this
245       *                     is empty or {@code null}, then all user attributes
246       *                     will be returned.
247       *
248       * @return  An ASN.1 octet string that can be used as the value for this
249       *          control.
250       */
251      private static ASN1OctetString encodeValue(final String[] attributes)
252      {
253        if ((attributes == null) || (attributes.length == 0))
254        {
255          return new ASN1OctetString(new ASN1Sequence().encode());
256        }
257    
258        final ASN1OctetString[] elements = new ASN1OctetString[attributes.length];
259        for (int i=0; i < attributes.length; i++)
260        {
261          elements[i] = new ASN1OctetString(attributes[i]);
262        }
263    
264        return new ASN1OctetString(new ASN1Sequence(elements).encode());
265      }
266    
267    
268    
269      /**
270       * Retrieves the set of attributes that will be requested for inclusion in the
271       * entry returned in the response control.
272       *
273       * @return  The set of attributes that will be requested for inclusion in the
274       *          entry returned in the response control, or an empty array if all
275       *          user attributes should be returned.
276       */
277      public String[] getAttributes()
278      {
279        return attributes;
280      }
281    
282    
283    
284      /**
285       * {@inheritDoc}
286       */
287      @Override()
288      public String getControlName()
289      {
290        return INFO_CONTROL_NAME_POST_READ_REQUEST.get();
291      }
292    
293    
294    
295      /**
296       * {@inheritDoc}
297       */
298      @Override()
299      public void toString(final StringBuilder buffer)
300      {
301        buffer.append("PostReadRequestControl(attributes={");
302        for (int i=0; i < attributes.length; i++)
303        {
304          if (i > 0)
305          {
306            buffer.append(", ");
307          }
308          buffer.append('\'');
309          buffer.append(attributes[i]);
310          buffer.append('\'');
311        }
312        buffer.append("}, isCritical=");
313        buffer.append(isCritical());
314        buffer.append(')');
315      }
316    }