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