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