001/* 002 * Copyright 2018-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2018-2024 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) 2018-2024 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.logs; 037 038 039 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.List; 043 044import com.unboundid.ldap.sdk.Attribute; 045import com.unboundid.ldap.sdk.ChangeType; 046import com.unboundid.ldap.sdk.ReadOnlyEntry; 047import com.unboundid.ldap.sdk.unboundidds.controls.UndeleteRequestControl; 048import com.unboundid.ldif.LDIFAddChangeRecord; 049import com.unboundid.ldif.LDIFChangeRecord; 050import com.unboundid.ldif.LDIFDeleteChangeRecord; 051import com.unboundid.ldif.LDIFException; 052import com.unboundid.ldif.LDIFReader; 053import com.unboundid.util.Debug; 054import com.unboundid.util.NotNull; 055import com.unboundid.util.Nullable; 056import com.unboundid.util.StaticUtils; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059 060import static com.unboundid.ldap.sdk.unboundidds.logs.LogMessages.*; 061 062 063 064/** 065 * This class provides a data structure that holds information about an audit 066 * log message that represents a delete operation. 067 * <BR> 068 * <BLOCKQUOTE> 069 * <B>NOTE:</B> This class, and other classes within the 070 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 071 * supported for use against Ping Identity, UnboundID, and 072 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 073 * for proprietary functionality or for external specifications that are not 074 * considered stable or mature enough to be guaranteed to work in an 075 * interoperable way with other types of LDAP servers. 076 * </BLOCKQUOTE> 077 */ 078@ThreadSafety(level= ThreadSafetyLevel.COMPLETELY_THREADSAFE) 079public final class DeleteAuditLogMessage 080 extends AuditLogMessage 081{ 082 /** 083 * Retrieves the serial version UID for this serializable class. 084 */ 085 private static final long serialVersionUID = 2082830761413726711L; 086 087 088 089 // Indicates whether the entry was deleted as part of a subtree delete. 090 @Nullable private final Boolean deletedAsPartOfSubtreeDelete; 091 092 // Indicates whether the delete operation represents a subtree delete. 093 @Nullable private final Boolean isSubtreeDelete; 094 095 // Indicates whether the delete operation represents a soft delete. 096 @Nullable private final Boolean isSoftDelete; 097 098 // Indicates whether the delete operation targets a soft-deleted entry. 099 @Nullable private final Boolean isSoftDeletedEntry; 100 101 // An LDIF change record that encapsulates the change represented by this 102 // delete audit log message. 103 @NotNull private final LDIFDeleteChangeRecord deleteChangeRecord; 104 105 // A list of the virtual attributes from the entry that was deleted. 106 @Nullable private final List<Attribute> deletedEntryVirtualAttributes; 107 108 // A read-only copy of the entry that was deleted. 109 @Nullable private final ReadOnlyEntry deletedEntry; 110 111 // The resulting DN of the soft-deleted entry. 112 @Nullable private final String softDeletedEntryDN; 113 114 115 116 /** 117 * Creates a new delete audit log message from the provided set of lines. 118 * 119 * @param logMessageLines The lines that comprise the log message. It must 120 * not be {@code null} or empty, and it must not 121 * contain any blank lines, although it may contain 122 * comments. In fact, it must contain at least one 123 * comment line that appears before any non-comment 124 * lines (but possibly after other comment lines) 125 * that serves as the message header. 126 * 127 * @throws AuditLogException If a problem is encountered while processing 128 * the provided list of log message lines. 129 */ 130 public DeleteAuditLogMessage(@NotNull final String... logMessageLines) 131 throws AuditLogException 132 { 133 this(StaticUtils.toList(logMessageLines), logMessageLines); 134 } 135 136 137 138 /** 139 * Creates a new delete audit log message from the provided set of lines. 140 * 141 * @param logMessageLines The lines that comprise the log message. It must 142 * not be {@code null} or empty, and it must not 143 * contain any blank lines, although it may contain 144 * comments. In fact, it must contain at least one 145 * comment line that appears before any non-comment 146 * lines (but possibly after other comment lines) 147 * that serves as the message header. 148 * 149 * @throws AuditLogException If a problem is encountered while processing 150 * the provided list of log message lines. 151 */ 152 public DeleteAuditLogMessage(@NotNull final List<String> logMessageLines) 153 throws AuditLogException 154 { 155 this(logMessageLines, StaticUtils.toArray(logMessageLines, String.class)); 156 } 157 158 159 160 /** 161 * Creates a new delete audit log message from the provided information. 162 * 163 * @param logMessageLineList The lines that comprise the log message as a 164 * list. 165 * @param logMessageLineArray The lines that comprise the log message as an 166 * array. 167 * 168 * @throws AuditLogException If a problem is encountered while processing 169 * the provided list of log message lines. 170 */ 171 private DeleteAuditLogMessage(@NotNull final List<String> logMessageLineList, 172 @NotNull final String[] logMessageLineArray) 173 throws AuditLogException 174 { 175 super(logMessageLineList); 176 177 try 178 { 179 final LDIFChangeRecord changeRecord = 180 LDIFReader.decodeChangeRecord(logMessageLineArray); 181 if (! (changeRecord instanceof LDIFDeleteChangeRecord)) 182 { 183 throw new AuditLogException(logMessageLineList, 184 ERR_DELETE_AUDIT_LOG_MESSAGE_CHANGE_TYPE_NOT_DELETE.get( 185 changeRecord.getChangeType().getName(), 186 ChangeType.DELETE.getName())); 187 } 188 189 deleteChangeRecord = (LDIFDeleteChangeRecord) changeRecord; 190 } 191 catch (final LDIFException e) 192 { 193 Debug.debugException(e); 194 throw new AuditLogException(logMessageLineList, 195 ERR_DELETE_AUDIT_LOG_MESSAGE_LINES_NOT_CHANGE_RECORD.get( 196 StaticUtils.getExceptionMessage(e)), 197 e); 198 } 199 200 deletedAsPartOfSubtreeDelete = getNamedValueAsBoolean( 201 "deletedAsPartOfSubtreeDelete", getHeaderNamedValues()); 202 isSubtreeDelete = 203 getNamedValueAsBoolean("isSubtreeDelete", getHeaderNamedValues()); 204 isSoftDelete = 205 getNamedValueAsBoolean("isSoftDelete", getHeaderNamedValues()); 206 isSoftDeletedEntry = 207 getNamedValueAsBoolean("isSoftDeletedEntry", getHeaderNamedValues()); 208 softDeletedEntryDN = getHeaderNamedValues().get("softDeletedEntryDN"); 209 deletedEntry = decodeCommentedEntry("Deleted entry real attributes", 210 logMessageLineList, deleteChangeRecord.getDN()); 211 212 final ReadOnlyEntry virtualAttributeEntry = decodeCommentedEntry( 213 "Deleted entry virtual attributes", logMessageLineList, 214 deleteChangeRecord.getDN()); 215 if (virtualAttributeEntry == null) 216 { 217 deletedEntryVirtualAttributes = null; 218 } 219 else 220 { 221 deletedEntryVirtualAttributes = Collections.unmodifiableList( 222 new ArrayList<>(virtualAttributeEntry.getAttributes())); 223 } 224 } 225 226 227 228 /** 229 * Creates a new delete audit log message from the provided set of lines. 230 * 231 * @param logMessageLines The lines that comprise the log message. It 232 * must not be {@code null} or empty, and it must 233 * not contain any blank lines, although it may 234 * contain comments. In fact, it must contain at 235 * least one comment line that appears before any 236 * non-comment lines (but possibly after other 237 * comment lines) that serves as the message 238 * header. 239 * @param deleteChangeRecord The LDIF delete change record that is described 240 * by the provided log message lines. 241 * 242 * @throws AuditLogException If a problem is encountered while processing 243 * the provided list of log message lines. 244 */ 245 DeleteAuditLogMessage(@NotNull final List<String> logMessageLines, 246 @NotNull final LDIFDeleteChangeRecord deleteChangeRecord) 247 throws AuditLogException 248 { 249 super(logMessageLines); 250 251 this.deleteChangeRecord = deleteChangeRecord; 252 253 deletedAsPartOfSubtreeDelete = getNamedValueAsBoolean( 254 "deletedAsPartOfSubtreeDelete", getHeaderNamedValues()); 255 isSubtreeDelete = 256 getNamedValueAsBoolean("isSubtreeDelete", getHeaderNamedValues()); 257 isSoftDelete = 258 getNamedValueAsBoolean("isSoftDelete", getHeaderNamedValues()); 259 isSoftDeletedEntry = 260 getNamedValueAsBoolean("isSoftDeletedEntry", getHeaderNamedValues()); 261 softDeletedEntryDN = getHeaderNamedValues().get("softDeletedEntryDN"); 262 deletedEntry = decodeCommentedEntry("Deleted entry real attributes", 263 logMessageLines, deleteChangeRecord.getDN()); 264 265 final ReadOnlyEntry virtualAttributeEntry = decodeCommentedEntry( 266 "Deleted entry virtual attributes", logMessageLines, 267 deleteChangeRecord.getDN()); 268 if (virtualAttributeEntry == null) 269 { 270 deletedEntryVirtualAttributes = null; 271 } 272 else 273 { 274 deletedEntryVirtualAttributes = Collections.unmodifiableList( 275 new ArrayList<>(virtualAttributeEntry.getAttributes())); 276 } 277 } 278 279 280 281 /** 282 * {@inheritDoc} 283 */ 284 @Override() 285 @NotNull() 286 public String getDN() 287 { 288 return deleteChangeRecord.getDN(); 289 } 290 291 292 293 /** 294 * Retrieves the value of the flag that indicates whether this delete audit 295 * log message represents the delete of the base entry of a subtree delete 296 * operation, if available. 297 * 298 * @return {@code Boolean.TRUE} if it is known that the operation was a 299 * subtree delete, {@code Boolean.FALSE} if it is known that the 300 * operation was not a subtree delete, or {@code null} if this is not 301 * available. 302 */ 303 @Nullable() 304 public Boolean getIsSubtreeDelete() 305 { 306 return isSubtreeDelete; 307 } 308 309 310 311 /** 312 * Retrieves the value of the flag that indicates whether this delete audit 313 * log record represents an entry that was deleted as part of a subtree 314 * delete (and is not the base entry for that subtree delete), if available. 315 * 316 * @return {@code Boolean.TRUE} if it is known that the entry was deleted as 317 * part of a subtree delete, {@code Boolean.FALSE} if it is known 318 * that the entry was not deleted as part of a subtree delete, or 319 * {@code null} if this is not available. 320 */ 321 @Nullable() 322 public Boolean getDeletedAsPartOfSubtreeDelete() 323 { 324 return deletedAsPartOfSubtreeDelete; 325 } 326 327 328 329 /** 330 * Retrieves the value of the flag that indicates whether this delete 331 * operation was a soft delete, if available. 332 * 333 * @return {@code Boolean.TRUE} if it is known that the operation was a soft 334 * delete, {@code Boolean.FALSE} if it is known that the operation 335 * was not a soft delete, or {@code null} if this is not available. 336 */ 337 @Nullable() 338 public Boolean getIsSoftDelete() 339 { 340 return isSoftDelete; 341 } 342 343 344 345 /** 346 * Retrieves the DN of the entry after it was been soft deleted, if available. 347 * 348 * @return The DN of the entry after it was soft deleted, or {@code null} if 349 * this is not available. 350 */ 351 @Nullable() 352 public String getSoftDeletedEntryDN() 353 { 354 return softDeletedEntryDN; 355 } 356 357 358 359 /** 360 * Retrieves the value of the flag that indicates whether this delete 361 * operation targeted an entry that had previously been soft deleted, if 362 * available. 363 * 364 * @return {@code Boolean.TRUE} if it is known that the operation targeted a 365 * soft-deleted entry, {@code Boolean.FALSE} if it is known that the 366 * operation did not target a soft-deleted entry, or {@code null} if 367 * this is not available. 368 */ 369 @Nullable() 370 public Boolean getIsSoftDeletedEntry() 371 { 372 return isSoftDeletedEntry; 373 } 374 375 376 377 /** 378 * Retrieves a read-only copy of the entry that was deleted, if available. 379 * 380 * @return A read-only copy of the entry that was deleted, or {@code null} if 381 * it is not available. 382 */ 383 @Nullable() 384 public ReadOnlyEntry getDeletedEntry() 385 { 386 return deletedEntry; 387 } 388 389 390 391 /** 392 * Retrieves a list of the virtual attributes from the entry that was deleted, 393 * if available. 394 * 395 * @return A list of the virtual attributes from the entry that was deleted, 396 * or {@code null} if it is not available. 397 */ 398 @Nullable() 399 public List<Attribute> getDeletedEntryVirtualAttributes() 400 { 401 return deletedEntryVirtualAttributes; 402 } 403 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override() 410 @NotNull() 411 public ChangeType getChangeType() 412 { 413 return ChangeType.DELETE; 414 } 415 416 417 418 /** 419 * {@inheritDoc} 420 */ 421 @Override() 422 @NotNull() 423 public LDIFDeleteChangeRecord getChangeRecord() 424 { 425 return deleteChangeRecord; 426 } 427 428 429 430 /** 431 * {@inheritDoc} 432 */ 433 @Override() 434 public boolean isRevertible() 435 { 436 // Subtree delete operations are not inherently revertible. The audit log 437 // should actually record a separate delete log message for each entry that 438 // was deleted as part of the subtree delete, and therefore it is possible 439 // to reverse an audit log that includes those additional delete records, 440 // but it is not possible to revert a subtree delete from a single delete 441 // audit log message. 442 // 443 // However, if this audit log message is for the base entry of a subtree 444 // delete, and if getDeletedEntry returns a non-null value, then the add 445 // change record needed to revert the delete of just that base entry can be 446 // obtained by simply creating an add change record using the entry returned 447 // by getDeletedEntry. 448 if ((isSubtreeDelete != null) && isSubtreeDelete) 449 { 450 return false; 451 } 452 453 // Non-subtree delete audit log messages are revertible under conditions: 454 // - It was a soft delete and we have the soft-deleted entry DN. 455 // - It was a hard delete and we have a copy of the entry that was deleted. 456 if ((isSoftDelete != null) && isSoftDelete) 457 { 458 return (softDeletedEntryDN != null); 459 } 460 else 461 { 462 return (deletedEntry != null); 463 } 464 } 465 466 467 468 /** 469 * {@inheritDoc} 470 */ 471 @Override() 472 @NotNull() 473 public List<LDIFChangeRecord> getRevertChangeRecords() 474 throws AuditLogException 475 { 476 if ((isSubtreeDelete != null) && isSubtreeDelete) 477 { 478 if (deletedEntry == null) 479 { 480 throw new AuditLogException(getLogMessageLines(), 481 ERR_DELETE_AUDIT_LOG_MESSAGE_SUBTREE_DELETE_WITHOUT_ENTRY.get( 482 deleteChangeRecord.getDN())); 483 } 484 else 485 { 486 throw new AuditLogException(getLogMessageLines(), 487 ERR_DELETE_AUDIT_LOG_MESSAGE_SUBTREE_DELETE_WITH_ENTRY.get( 488 deleteChangeRecord.getDN())); 489 } 490 } 491 492 if ((isSoftDelete != null) && isSoftDelete) 493 { 494 if (softDeletedEntryDN != null) 495 { 496 return Collections.<LDIFChangeRecord>singletonList( 497 new LDIFAddChangeRecord( 498 UndeleteRequestControl.createUndeleteRequest( 499 deleteChangeRecord.getDN(), softDeletedEntryDN))); 500 } 501 else 502 { 503 throw new AuditLogException(getLogMessageLines(), 504 ERR_DELETE_AUDIT_LOG_MESSAGE_NO_SOFT_DELETED_ENTRY_DN.get( 505 deleteChangeRecord.getDN())); 506 } 507 } 508 else 509 { 510 if (deletedEntry != null) 511 { 512 return Collections.<LDIFChangeRecord>singletonList( 513 new LDIFAddChangeRecord(deletedEntry)); 514 } 515 else 516 { 517 throw new AuditLogException(getLogMessageLines(), 518 ERR_DELETE_AUDIT_LOG_MESSAGE_DELETED_ENTRY.get( 519 deleteChangeRecord.getDN())); 520 } 521 } 522 } 523 524 525 526 /** 527 * {@inheritDoc} 528 */ 529 @Override() 530 public void toString(@NotNull final StringBuilder buffer) 531 { 532 buffer.append(getUncommentedHeaderLine()); 533 buffer.append("; changeType=delete; dn=\""); 534 buffer.append(deleteChangeRecord.getDN()); 535 buffer.append('\"'); 536 } 537}