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