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