001/* 002 * Copyright 2012-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-2019 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 060 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 061 * for proprietary functionality or for external specifications that are not 062 * considered stable or mature enough to be guaranteed to work in an 063 * interoperable way with 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<>(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}