001    /*
002     * Copyright 2012-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 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.unboundidds.controls;
022    
023    
024    
025    import java.util.ArrayList;
026    
027    import com.unboundid.asn1.ASN1Boolean;
028    import com.unboundid.asn1.ASN1Element;
029    import com.unboundid.asn1.ASN1OctetString;
030    import com.unboundid.asn1.ASN1Sequence;
031    import com.unboundid.ldap.sdk.Control;
032    import com.unboundid.ldap.sdk.DeleteRequest;
033    import com.unboundid.ldap.sdk.LDAPException;
034    import com.unboundid.ldap.sdk.ResultCode;
035    import com.unboundid.util.Debug;
036    import com.unboundid.util.NotMutable;
037    import com.unboundid.util.StaticUtils;
038    import com.unboundid.util.ThreadSafety;
039    import com.unboundid.util.ThreadSafetyLevel;
040    
041    import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
042    
043    
044    
045    /**
046     * <BLOCKQUOTE>
047     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
048     *   LDAP SDK for Java.  It is not available for use in applications that
049     *   include only the Standard Edition of the LDAP SDK, and is not supported for
050     *   use in conjunction with non-UnboundID products.
051     * </BLOCKQUOTE>
052     * This class provides a request control which may be included in a delete
053     * request to indicate that the server should perform a soft delete rather than
054     * a hard delete.  A soft delete will leave the entry in the server, but will
055     * mark it hidden so that it can only be retrieved with a special request
056     * (e.g., one which includes the {@link SoftDeletedEntryAccessRequestControl} or
057     * a filter which includes an "(objectClass=ds-soft-deleted-entry)" component).
058     * A soft-deleted entry may later be undeleted (using an add request containing
059     * the {@link UndeleteRequestControl}) in order to restore them with the same or
060     * a different DN.
061     * <BR><BR>
062     * The criticality for this control may be either {@code TRUE} or {@code FALSE},
063     * but this will only impact how the delete request is to be handled by servers
064     * which do not support this control.  A criticality of {@code TRUE} will cause
065     * any server which does not support this control to reject the request, while
066     * a criticality of {@code FALSE} should cause the delete request to be
067     * processed as if the control had not been included (i.e., as a regular "hard"
068     * delete).
069     * <BR><BR>
070     * The control may optionally have a value.  If a value is provided, then it
071     * must be the encoded representation of the following ASN.1 element:
072     * <PRE>
073     *   SoftDeleteRequestValue ::= SEQUENCE {
074     *     returnSoftDeleteResponse     [0] BOOLEAN DEFAULT TRUE,
075     *     ... }
076     * </PRE>
077     * <BR><BR>
078     * <H2>Example</H2>
079     * The following example demonstrates the use of the soft delete request control
080     * to remove the "uid=test,dc=example,dc=com" user with a soft delete operation,
081     * and then to recover it with an undelete operation:
082     * <PRE>
083     * // Perform a search to verify that the test entry exists.
084     * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
085     *      SearchScope.SUB, Filter.createEqualityFilter("uid", "test"));
086     * SearchResult searchResult = connection.search(searchRequest);
087     * LDAPTestUtils.assertEntriesReturnedEquals(searchResult, 1);
088     * String originalDN = searchResult.getSearchEntries().get(0).getDN();
089     *
090     * // Perform a soft delete against the entry.
091     * DeleteRequest softDeleteRequest = new DeleteRequest(originalDN);
092     * softDeleteRequest.addControl(new SoftDeleteRequestControl());
093     * LDAPResult softDeleteResult = connection.delete(softDeleteRequest);
094     *
095     * // Verify that a soft delete response control was included in the result.
096     * SoftDeleteResponseControl softDeleteResponseControl =
097     *      SoftDeleteResponseControl.get(softDeleteResult);
098     * String softDeletedDN = softDeleteResponseControl.getSoftDeletedEntryDN();
099     *
100     * // Verify that the original entry no longer exists.
101     * LDAPTestUtils.assertEntryMissing(connection, originalDN);
102     *
103     * // Verify that the original search no longer returns any entries.
104     * searchResult = connection.search(searchRequest);
105     * LDAPTestUtils.assertNoEntriesReturned(searchResult);
106     *
107     * // Verify that the search will return an entry if we include the
108     * // soft-deleted entry access control in the request.
109     * searchRequest.addControl(new SoftDeletedEntryAccessRequestControl());
110     * searchResult = connection.search(searchRequest);
111     * LDAPTestUtils.assertEntriesReturnedEquals(searchResult, 1);
112     *
113     * // Perform an undelete operation to restore the entry.
114     * AddRequest undeleteRequest = UndeleteRequestControl.createUndeleteRequest(
115     *      originalDN, softDeletedDN);
116     * LDAPResult undeleteResult = connection.add(undeleteRequest);
117     *
118     * // Verify that the original entry is back.
119     * LDAPTestUtils.assertEntryExists(connection, originalDN);
120     *
121     * // Permanently remove the original entry with a hard delete.
122     * DeleteRequest hardDeleteRequest = new DeleteRequest(originalDN);
123     * hardDeleteRequest.addControl(new HardDeleteRequestControl());
124     * LDAPResult hardDeleteResult = connection.delete(hardDeleteRequest);
125     * </PRE>
126     * Note that this class provides convenience methods that can be used to easily
127     * create a delete request containing an appropriate soft delete request
128     * control.  Similar methods can be found in the
129     * {@link HardDeleteRequestControl} and {@link UndeleteRequestControl} classes
130     * for creating appropriate hard delete and undelete requests, respectively.
131     *
132     * @see  HardDeleteRequestControl
133     * @see  SoftDeleteResponseControl
134     * @see  SoftDeletedEntryAccessRequestControl
135     * @see  UndeleteRequestControl
136     */
137    @NotMutable()
138    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
139    public final class SoftDeleteRequestControl
140           extends Control
141    {
142      /**
143       * The OID (1.3.6.1.4.1.30221.2.5.20) for the soft delete request control.
144       */
145      public static final String SOFT_DELETE_REQUEST_OID =
146           "1.3.6.1.4.1.30221.2.5.20";
147    
148    
149    
150      /**
151       * The BER type for the return soft delete response element.
152       */
153      private static final byte TYPE_RETURN_SOFT_DELETE_RESPONSE = (byte) 0x80;
154    
155    
156    
157      /**
158       * The serial version UID for this serializable class.
159       */
160      private static final long serialVersionUID = 4068029406430690545L;
161    
162    
163    
164      // Indicates whether to the response should include a soft delete response
165      // control.
166      private final boolean returnSoftDeleteResponse;
167    
168    
169    
170      /**
171       * Creates a new soft delete request control with the default settings for
172       * all elements.  It will be marked critical.
173       */
174      public SoftDeleteRequestControl()
175      {
176        this(true, true);
177      }
178    
179    
180    
181      /**
182       * Creates a new soft delete request control with the provided information.
183       *
184       * @param  isCritical                Indicates whether this control should be
185       *                                   marked critical.  This will only have an
186       *                                   effect on the way the associated delete
187       *                                   operation is handled by servers which do
188       *                                   NOT support the soft delete request
189       *                                   control.  For such servers, a control
190       *                                   that is critical will cause the soft
191       *                                   delete attempt to fail, while a control
192       *                                   that is not critical will be processed as
193       *                                   if the control was not included in the
194       *                                   request (i.e., as a normal "hard"
195       *                                   delete).
196       * @param  returnSoftDeleteResponse  Indicates whether to return a soft delete
197       *                                   response control in the delete response
198       *                                   to the client.
199       */
200      public SoftDeleteRequestControl(final boolean isCritical,
201                                      final boolean returnSoftDeleteResponse)
202      {
203        super(SOFT_DELETE_REQUEST_OID, isCritical,
204             encodeValue(returnSoftDeleteResponse));
205    
206        this.returnSoftDeleteResponse = returnSoftDeleteResponse;
207      }
208    
209    
210    
211      /**
212       * Creates a new soft delete request control which is decoded from the
213       * provided generic control.
214       *
215       * @param  control  The generic control to be decoded as a soft delete request
216       *                  control.
217       *
218       * @throws  LDAPException  If the provided control cannot be decoded as a soft
219       *                         delete request control.
220       */
221      public SoftDeleteRequestControl(final Control control)
222             throws LDAPException
223      {
224        super(control);
225    
226        boolean returnResponse = true;
227        if (control.hasValue())
228        {
229          try
230          {
231            final ASN1Sequence valueSequence =
232                 ASN1Sequence.decodeAsSequence(control.getValue().getValue());
233            for (final ASN1Element e : valueSequence.elements())
234            {
235              switch (e.getType())
236              {
237                case TYPE_RETURN_SOFT_DELETE_RESPONSE:
238                  returnResponse = ASN1Boolean.decodeAsBoolean(e).booleanValue();
239                  break;
240                default:
241                  throw new LDAPException(ResultCode.DECODING_ERROR,
242                       ERR_SOFT_DELETE_REQUEST_UNSUPPORTED_VALUE_ELEMENT_TYPE.get(
243                            StaticUtils.toHex(e.getType())));
244              }
245            }
246          }
247          catch (final LDAPException le)
248          {
249            Debug.debugException(le);
250            throw le;
251          }
252          catch (final Exception e)
253          {
254            Debug.debugException(e);
255            throw new LDAPException(ResultCode.DECODING_ERROR,
256                 ERR_SOFT_DELETE_REQUEST_CANNOT_DECODE_VALUE.get(
257                      StaticUtils.getExceptionMessage(e)),
258                 e);
259          }
260        }
261    
262        returnSoftDeleteResponse = returnResponse;
263      }
264    
265    
266    
267      /**
268       * Encodes the provided information into an ASN.1 octet string suitable for
269       * use as the value of a soft delete request control.
270       *
271       * @param  returnSoftDeleteResponse  Indicates whether to return a soft delete
272       *                                   response control in the delete response
273       *                                   to the client.
274       *
275       * @return  An ASN.1 octet string with an encoding suitable for use as the
276       *          value of a soft delete request control, or {@code null} if no
277       *          value is needed for the control.
278       */
279      private static ASN1OctetString encodeValue(
280                                          final boolean returnSoftDeleteResponse)
281      {
282        if (returnSoftDeleteResponse)
283        {
284          return null;
285        }
286    
287        final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(1);
288        elements.add(new ASN1Boolean(TYPE_RETURN_SOFT_DELETE_RESPONSE, false));
289        return new ASN1OctetString(new ASN1Sequence(elements).encode());
290      }
291    
292    
293    
294      /**
295       * Indicates whether the delete response should include a
296       * {@link SoftDeleteResponseControl}.
297       *
298       * @return  {@code true} if the delete response should include a soft delete
299       *          response control, or {@code false} if not.
300       */
301      public boolean returnSoftDeleteResponse()
302      {
303        return returnSoftDeleteResponse;
304      }
305    
306    
307    
308      /**
309       * Creates a new delete request that may be used to soft delete the specified
310       * target entry.
311       *
312       * @param  targetDN                  The DN of the entry to be soft deleted.
313       * @param  isCritical                Indicates whether this control should be
314       *                                   marked critical.  This will only have an
315       *                                   effect on the way the associated delete
316       *                                   operation is handled by servers which do
317       *                                   NOT support the soft delete request
318       *                                   control.  For such servers, a control
319       *                                   that is critical will cause the soft
320       *                                   delete attempt to fail, while a control
321       *                                   that is not critical will be processed as
322       *                                   if the control was not included in the
323       *                                   request (i.e., as a normal "hard"
324       *                                   delete).
325       * @param  returnSoftDeleteResponse  Indicates whether to return a soft delete
326       *                                   response control in the delete response
327       *                                   to the client.
328       *
329       * @return  A delete request with the specified target DN and an appropriate
330       *          soft delete request control.
331       */
332      public static DeleteRequest createSoftDeleteRequest(final String targetDN,
333                                       final boolean isCritical,
334                                       final boolean returnSoftDeleteResponse)
335      {
336        final Control[] controls =
337        {
338          new SoftDeleteRequestControl(isCritical, returnSoftDeleteResponse)
339        };
340    
341        return new DeleteRequest(targetDN, controls);
342      }
343    
344    
345    
346      /**
347       * {@inheritDoc}
348       */
349      @Override()
350      public String getControlName()
351      {
352        return INFO_CONTROL_NAME_SOFT_DELETE_REQUEST.get();
353      }
354    
355    
356    
357      /**
358       * {@inheritDoc}
359       */
360      @Override()
361      public void toString(final StringBuilder buffer)
362      {
363        buffer.append("SoftDeleteRequestControl(isCritical=");
364        buffer.append(isCritical());
365        buffer.append(", returnSoftDeleteResponse=");
366        buffer.append(returnSoftDeleteResponse);
367        buffer.append(')');
368      }
369    }