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}