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 }