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}