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 }