001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.replication.plugin;
028
029import static org.opends.messages.ReplicationMessages.*;
030import static org.opends.server.replication.plugin.HistAttrModificationKey.*;
031
032import java.util.*;
033
034import org.forgerock.i18n.slf4j.LocalizedLogger;
035import org.forgerock.opendj.ldap.ByteString;
036import org.forgerock.opendj.ldap.ModificationType;
037import org.opends.server.core.DirectoryServer;
038import org.opends.server.replication.common.CSN;
039import org.opends.server.replication.protocol.OperationContext;
040import org.opends.server.types.*;
041import org.opends.server.types.operation.PreOperationAddOperation;
042import org.opends.server.types.operation.PreOperationModifyDNOperation;
043import org.opends.server.types.operation.PreOperationModifyOperation;
044import org.opends.server.util.TimeThread;
045
046/**
047 * This class is used to store historical information that is used to resolve modify conflicts
048 * <p>
049 * It is assumed that the common case is not to have conflict and therefore is optimized (in order
050 * of importance) for:
051 * <ol>
052 * <li>detecting potential conflict</li>
053 * <li>fast update of historical information for non-conflicting change</li>
054 * <li>fast and efficient purge</li>
055 * <li>compact</li>
056 * <li>solve conflict. This should also be as fast as possible but not at the cost of any of the
057 * other previous objectives</li>
058 * </ol>
059 * One Historical object is created for each entry in the entry cache each Historical Object
060 * contains a list of attribute historical information
061 */
062public class EntryHistorical
063{
064  /** Name of the attribute used to store historical information. */
065  public static final String HISTORICAL_ATTRIBUTE_NAME = "ds-sync-hist";
066  /**
067   * Name used to store attachment of historical information in the
068   * operation. This attachment allows to use in several different places
069   * the historical while reading/writing ONCE it from/to the entry.
070   */
071  public static final String HISTORICAL = "ds-synch-historical";
072  /** Name of the entryuuid attribute. */
073  public static final String ENTRYUUID_ATTRIBUTE_NAME = "entryuuid";
074
075  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
076
077  /**
078   * The delay to purge the historical information.
079   * <p>
080   * This delay indicates the time the domain keeps the historical information
081   * necessary to solve conflicts. When a change stored in the historical part
082   * of the user entry has a date (from its replication CSN) older than this
083   * delay, it is candidate to be purged. The purge is triggered on 2 events:
084   * modify of the entry, dedicated purge task. The purge is done when the
085   * historical is encoded.
086   */
087  private long purgeDelayInMillisec = -1;
088
089  /**
090   * The oldest CSN stored in this entry historical attribute.
091   * null when this historical object has been created from
092   * an entry that has no historical attribute and after the last
093   * historical has been purged.
094   */
095  private CSN oldestCSN;
096
097  /**
098   * For stats/monitoring purpose, the number of historical values
099   * purged the last time a purge has been applied on this entry historical.
100   */
101  private int lastPurgedValuesCount;
102
103  /**
104   * The in-memory historical information is made of.
105   *
106   * EntryHistorical ::= ADDDate MODDNDate attributesInfo
107   * ADDDate       ::= CSN  // the date the entry was added
108   * MODDNDate     ::= CSN  // the date the entry was last renamed
109   *
110   * attributesInfo      ::= (AttrInfoWithOptions)*
111   *                         one AttrInfoWithOptions by attributeType
112   *
113   * AttrInfoWithOptions ::= (AttributeInfo)*
114   *                         one AttributeInfo by attributeType and option
115   *
116   * AttributeInfo       ::= AttrInfoSingle | AttrInfoMultiple
117   *
118   * AttrInfoSingle      ::= AddTime DeleteTime ValueInfo
119   *
120   * AttrInfoMultiple    ::= AddTime DeleteTime ValuesInfo
121   *
122   * ValuesInfo          ::= (AttrValueHistorical)*
123   *                         AttrValueHistorical is the historical of the
124   *                         the modification of one value
125   *
126   * AddTime             ::= CSN // last time the attribute was added to the entry
127   * DeleteTime          ::= CSN // last time the attribute was deleted from the entry
128   *
129   * AttrValueHistorical ::= AttributeValue valueDeleteTime valueUpdateTime
130   * valueDeleteTime     ::= CSN
131   * valueUpdateTime     ::= CSN
132   *
133   * - a list indexed on AttributeType of AttrInfoWithOptions :
134   *     each value is the historical for this attribute
135   *     an AttrInfoWithOptions is a set indexed on the optionValue(string) of
136   *     AttributeInfo
137   */
138
139  /** The date when the entry was added. */
140  private CSN entryADDDate;
141
142  /** The date when the entry was last renamed. */
143  private CSN entryMODDNDate;
144
145  /**
146   * Contains Historical information for each attribute sorted by attribute
147   * type. key:AttributeType value:AttrInfoWithOptions
148   */
149  private final HashMap<AttributeType,AttrHistoricalWithOptions> attributesHistorical = new HashMap<>();
150
151  /** {@inheritDoc} */
152  @Override
153  public String toString()
154  {
155    StringBuilder builder = new StringBuilder();
156    builder.append(encodeAndPurge());
157    return builder.toString();
158  }
159
160  /**
161   * Process an operation.
162   * This method is responsible for detecting and resolving conflict for
163   * modifyOperation. This is done by using the historical information.
164   *
165   * @param modifyOperation the operation to be processed
166   * @param modifiedEntry the entry that is being modified (before modification)
167   * @return true if the replayed operation was in conflict
168   */
169  public boolean replayOperation(PreOperationModifyOperation modifyOperation, Entry modifiedEntry)
170  {
171    boolean bConflict = false;
172    List<Modification> mods = modifyOperation.getModifications();
173    CSN modOpCSN = OperationContext.getCSN(modifyOperation);
174
175    for (Iterator<Modification> it = mods.iterator(); it.hasNext(); )
176    {
177      Modification m = it.next();
178
179      // Read or create the attr historical for the attribute type and option
180      // contained in the mod
181      AttrHistorical attrHist = getOrCreateAttrHistorical(m);
182      if (attrHist.replayOperation(it, modOpCSN, modifiedEntry, m))
183      {
184        bConflict = true;
185      }
186    }
187
188    return bConflict;
189  }
190
191  /**
192   * Update the historical information for the provided operation.
193   * <p>
194   * Steps:
195   * <ul>
196   * <li>compute the historical attribute</li>
197   * <li>update the mods in the provided operation by adding the update of the
198   * historical attribute</li>
199   * <li>update the modifiedEntry, already computed by core since we are in the
200   * preOperation plugin, that is called just before committing into the DB.
201   * </li>
202   * </ul>
203   * </p>
204   *
205   * @param modifyOperation
206   *          the modification.
207   */
208  public void setHistoricalAttrToOperation(PreOperationModifyOperation modifyOperation)
209  {
210    List<Modification> mods = modifyOperation.getModifications();
211    Entry modifiedEntry = modifyOperation.getModifiedEntry();
212    CSN csn = OperationContext.getCSN(modifyOperation);
213
214    /*
215     * If this is a local operation we need :
216     * - first to update the historical information,
217     * - then update the entry with the historical information
218     * If this is a replicated operation the historical information has
219     * already been set in the resolveConflict phase and we only need
220     * to update the entry
221     */
222    if (!modifyOperation.isSynchronizationOperation())
223    {
224      for (Modification mod : mods)
225      {
226        // Get the current historical for this attributeType/options
227        // (eventually read from the provided modification)
228        AttrHistorical attrHist = getOrCreateAttrHistorical(mod);
229        if (attrHist != null)
230        {
231          attrHist.processLocalOrNonConflictModification(csn, mod);
232        }
233      }
234    }
235
236    // Now do the 2 updates required by the core to be consistent:
237    //
238    // - add the modification of the ds-sync-hist attribute,
239    // to the current modifications of the MOD operation
240    Attribute attr = encodeAndPurge();
241    mods.add(new Modification(ModificationType.REPLACE, attr));
242    // - update the already modified entry
243    modifiedEntry.replaceAttribute(attr);
244  }
245
246  /**
247   * For a MODDN operation, add new or update existing historical information.
248   * <p>
249   * This method is NOT static because it relies on this Historical object created in the
250   * HandleConflictResolution phase.
251   *
252   * @param modifyDNOperation
253   *          the modification for which the historical information should be created.
254   */
255  public void setHistoricalAttrToOperation(PreOperationModifyDNOperation modifyDNOperation)
256  {
257    // Update this historical information with the operation CSN.
258    this.entryMODDNDate = OperationContext.getCSN(modifyDNOperation);
259
260    // Update the operations mods and the modified entry so that the
261    // historical information gets stored in the DB and indexed accordingly.
262    Entry modifiedEntry = modifyDNOperation.getUpdatedEntry();
263    List<Modification> mods = modifyDNOperation.getModifications();
264
265    Attribute attr = encodeAndPurge();
266
267    // Now do the 2 updates required by the core to be consistent:
268    //
269    // - add the modification of the ds-sync-hist attribute,
270    // to the current modifications of the operation
271    mods.add(new Modification(ModificationType.REPLACE, attr));
272    // - update the already modified entry
273    modifiedEntry.removeAttribute(attr.getAttributeType());
274    modifiedEntry.addAttribute(attr, null);
275  }
276
277  /**
278   * Generate an attribute containing the historical information
279   * from the replication context attached to the provided operation
280   * and set this attribute in the operation.
281   *
282   *   For ADD, the historical is made of the CSN read from the
283   *   synchronization context attached to the operation.
284   *
285   *   Called for both local and synchronization ADD preOperation.
286   *
287   *   This historical information will be used to generate fake operation
288   *   in case a Directory Server can not find a Replication Server with
289   *   all its changes at connection time.
290   *   This should only happen if a Directory Server or a Replication Server
291   *   crashes.
292   *
293   *   This method is static because there is no Historical object creation
294   *   required here or before(in the HandleConflictResolution phase)
295   *
296   * @param addOperation The Operation to which the historical attribute will be added.
297   */
298  public static void setHistoricalAttrToOperation(PreOperationAddOperation addOperation)
299  {
300    AttributeType attrType = DirectoryServer.getAttributeType(HISTORICAL_ATTRIBUTE_NAME);
301    String attrValue = encodeHistorical(OperationContext.getCSN(addOperation), "add");
302    List<Attribute> attrs = Attributes.createAsList(attrType, attrValue);
303    addOperation.setAttribute(attrType, attrs);
304  }
305
306  /**
307   * Builds an attributeValue for the supplied historical information and
308   * operation type . For ADD Operation : "dn:changeNumber:add", for MODDN
309   * Operation : "dn:changeNumber:moddn", etc.
310   *
311   * @param csn
312   *          The date when the ADD Operation happened.
313   * @param operationType
314   *          the operation type to encode
315   * @return The attribute value containing the historical information for the Operation type.
316   */
317  private static String encodeHistorical(CSN csn, String operationType)
318  {
319    return "dn:" + csn + ":" + operationType;
320  }
321
322  /**
323   * Return an AttributeHistorical corresponding to the attribute type
324   * and options contained in the provided mod,
325   * The attributeHistorical is :
326   * - either read from this EntryHistorical object if one exist,
327   * - or created empty.
328   * Should never return null.
329   *
330   * @param  mod the provided mod from which we'll use attributeType
331   *             and options to retrieve/create the attribute historical
332   * @return the attribute historical retrieved or created empty.
333   */
334  private AttrHistorical getOrCreateAttrHistorical(Modification mod)
335  {
336    // Read the provided mod
337    Attribute modAttr = mod.getAttribute();
338    if (isHistoricalAttribute(modAttr))
339    {
340      // Don't keep historical information for the attribute that is
341      // used to store the historical information.
342      return null;
343    }
344    Set<String> modOptions = modAttr.getOptions();
345    AttributeType modAttrType = modAttr.getAttributeType();
346
347    // Read from this entryHistorical,
348    // Create one empty if none was existing in this entryHistorical.
349    AttrHistoricalWithOptions attrHistWithOptions = attributesHistorical.get(modAttrType);
350    AttrHistorical attrHist;
351    if (attrHistWithOptions != null)
352    {
353      attrHist = attrHistWithOptions.get(modOptions);
354    }
355    else
356    {
357      attrHistWithOptions = new AttrHistoricalWithOptions();
358      attributesHistorical.put(modAttrType, attrHistWithOptions);
359      attrHist = null;
360    }
361
362    if (attrHist == null)
363    {
364      attrHist = AttrHistorical.createAttributeHistorical(modAttrType);
365      attrHistWithOptions.put(modOptions, attrHist);
366    }
367    return attrHist;
368  }
369
370  /**
371   * For stats/monitoring purpose, returns the number of historical values
372   * purged the last time a purge has been applied on this entry historical.
373   *
374   * @return the purged values count.
375   */
376  public int getLastPurgedValuesCount()
377  {
378    return this.lastPurgedValuesCount;
379  }
380
381  /**
382   * Encode this historical information object in an operational attribute and
383   * purge it from the values older than the purge delay.
384   *
385   * @return The historical information encoded in an operational attribute.
386   * @see HistoricalAttributeValue#HistoricalAttributeValue(String) the decode
387   *      operation in HistoricalAttributeValue
388   */
389  public Attribute encodeAndPurge()
390  {
391    long purgeDate = 0;
392
393    // Set the stats counter to 0 and compute the purgeDate to now minus
394    // the potentially set purge delay.
395    this.lastPurgedValuesCount = 0;
396    if (purgeDelayInMillisec>0)
397    {
398      purgeDate = TimeThread.getTime() - purgeDelayInMillisec;
399    }
400
401    AttributeType historicalAttrType = DirectoryServer.getAttributeType(HISTORICAL_ATTRIBUTE_NAME);
402    AttributeBuilder builder = new AttributeBuilder(historicalAttrType);
403
404    for (Map.Entry<AttributeType, AttrHistoricalWithOptions> entryWithOptions :
405          attributesHistorical.entrySet())
406    {
407      // Encode an attribute type
408      AttributeType type = entryWithOptions.getKey();
409      Map<Set<String>, AttrHistorical> attrWithOptions =
410                                entryWithOptions.getValue().getAttributesInfo();
411
412      for (Map.Entry<Set<String>, AttrHistorical> entry : attrWithOptions.entrySet())
413      {
414        // Encode an (attribute type/option)
415        String optionsString = toOptionsString(entry.getKey());
416        AttrHistorical attrHist = entry.getValue();
417
418        CSN deleteTime = attrHist.getDeleteTime();
419        /* generate the historical information for deleted attributes */
420        boolean attrDel = deleteTime != null;
421
422        for (AttrValueHistorical attrValHist : attrHist.getValuesHistorical())
423        {
424          final ByteString value = attrValHist.getAttributeValue();
425
426          // Encode an attribute value
427          if (attrValHist.getValueDeleteTime() != null)
428          {
429            if (needsPurge(attrValHist.getValueDeleteTime(), purgeDate))
430            {
431              // this hist must be purged now, so skip its encoding
432              continue;
433            }
434            String strValue = encode(DEL, type, optionsString, attrValHist.getValueDeleteTime(), value);
435            builder.add(strValue);
436          }
437          else if (attrValHist.getValueUpdateTime() != null)
438          {
439            if (needsPurge(attrValHist.getValueUpdateTime(), purgeDate))
440            {
441              // this hist must be purged now, so skip its encoding
442              continue;
443            }
444
445            String strValue;
446            final CSN updateTime = attrValHist.getValueUpdateTime();
447            // FIXME very suspicious use of == in the next if statement,
448            // unit tests do not like changing it
449            if (attrDel && updateTime == deleteTime && value != null)
450            {
451              strValue = encode(REPL, type, optionsString, updateTime, value);
452              attrDel = false;
453            }
454            else if (value != null)
455            {
456              strValue = encode(ADD, type, optionsString, updateTime, value);
457            }
458            else
459            {
460              // "add" without any value is suspicious. Tests never go there.
461              // Is this used to encode "add" with an empty string?
462              strValue = encode(ADD, type, optionsString, updateTime);
463            }
464
465            builder.add(strValue);
466          }
467        }
468
469        if (attrDel)
470        {
471          if (needsPurge(deleteTime, purgeDate))
472          {
473            // this hist must be purged now, so skip its encoding
474            continue;
475          }
476          String strValue = encode(ATTRDEL, type, optionsString, deleteTime);
477          builder.add(strValue);
478        }
479      }
480    }
481
482    if (entryADDDate != null && !needsPurge(entryADDDate, purgeDate))
483    {
484      // Encode the historical information for the ADD Operation.
485      // Stores the ADDDate when not older than the purge delay
486      builder.add(encodeHistorical(entryADDDate, "add"));
487    }
488
489    if (entryMODDNDate != null && !needsPurge(entryMODDNDate, purgeDate))
490    {
491      // Encode the historical information for the MODDN Operation.
492      // Stores the MODDNDate when not older than the purge delay
493      builder.add(encodeHistorical(entryMODDNDate, "moddn"));
494    }
495
496    return builder.toAttribute();
497  }
498
499  private String toOptionsString(Set<String> options)
500  {
501    if (options != null)
502    {
503      StringBuilder optionsBuilder = new StringBuilder();
504      for (String s : options)
505      {
506        optionsBuilder.append(';').append(s);
507      }
508      return optionsBuilder.toString();
509    }
510    return "";
511  }
512
513  private boolean needsPurge(CSN csn, long purgeDate)
514  {
515    boolean needsPurge = purgeDelayInMillisec > 0 && csn.getTime() <= purgeDate;
516    if (needsPurge)
517    {
518      // this hist must be purged now, because older than the purge delay
519      this.lastPurgedValuesCount++;
520    }
521    return needsPurge;
522  }
523
524  private String encode(HistAttrModificationKey modKey, AttributeType type,
525      String optionsString, CSN changeTime)
526  {
527    return type.getNormalizedPrimaryName() + optionsString + ":" + changeTime + ":" + modKey;
528  }
529
530  private String encode(HistAttrModificationKey modKey, AttributeType type,
531      String optionsString, CSN changeTime, ByteString value)
532  {
533    return type.getNormalizedPrimaryName() + optionsString + ":" + changeTime + ":" + modKey + ":" + value;
534  }
535
536  /**
537   * Set the delay to purge the historical information. The purge is applied
538   * only when historical attribute is updated (write operations).
539   *
540   * @param purgeDelay the purge delay in ms
541   */
542  public void setPurgeDelay(long purgeDelay)
543  {
544    this.purgeDelayInMillisec = purgeDelay;
545  }
546
547  /**
548   * Indicates if the Entry was renamed or added after the CSN that is given as
549   * a parameter.
550   *
551   * @param csn
552   *          The CSN with which the ADD or Rename date must be compared.
553   * @return A boolean indicating if the Entry was renamed or added after the
554   *         CSN that is given as a parameter.
555   */
556  public boolean addedOrRenamedAfter(CSN csn)
557  {
558    return csn.isOlderThan(entryADDDate) || csn.isOlderThan(entryMODDNDate);
559  }
560
561  /**
562   * Returns the lastCSN when the entry DN was modified.
563   *
564   * @return The lastCSN when the entry DN was modified.
565   */
566  public CSN getDNDate()
567  {
568    if (entryADDDate == null)
569    {
570      return entryMODDNDate;
571    }
572    if (entryMODDNDate == null)
573    {
574      return entryADDDate;
575    }
576
577    if (entryMODDNDate.isOlderThan(entryADDDate))
578    {
579      return entryMODDNDate;
580    }
581    else
582    {
583      return entryADDDate;
584    }
585  }
586
587  /**
588   * Construct an Historical object from the provided entry by reading the historical attribute.
589   * Return an empty object when the entry does not contain any historical attribute.
590   *
591   * @param entry The entry which historical information must be loaded
592   * @return The constructed Historical information object
593   */
594  public static EntryHistorical newInstanceFromEntry(Entry entry)
595  {
596    // Read the DB historical attribute from the entry
597    List<Attribute> histAttrWithOptionsFromEntry = getHistoricalAttr(entry);
598
599    // Now we'll build the Historical object we want to construct
600    final EntryHistorical newHistorical = new EntryHistorical();
601    if (histAttrWithOptionsFromEntry == null)
602    {
603      // No historical attribute in the entry, return empty object
604      return newHistorical;
605    }
606
607    try
608    {
609      AttributeType lastAttrType = null;
610      Set<String> lastOptions = new HashSet<>();
611      AttrHistorical attrInfo = null;
612      AttrHistoricalWithOptions attrInfoWithOptions = null;
613
614      // For each value of the historical attr read (mod. on a user attribute)
615      //   build an AttrInfo sub-object
616
617      // Traverse the Attributes (when several options for the hist attr)
618      // of the historical attribute read from the entry
619      for (Attribute histAttrFromEntry : histAttrWithOptionsFromEntry)
620      {
621        // For each Attribute (option), traverse the values
622        for (ByteString histAttrValueFromEntry : histAttrFromEntry)
623        {
624          // From each value of the hist attr, create an object
625          final HistoricalAttributeValue histVal = new HistoricalAttributeValue(histAttrValueFromEntry.toString());
626          final CSN csn = histVal.getCSN();
627
628          // update the oldest CSN stored in the new entry historical
629          newHistorical.updateOldestCSN(csn);
630
631          if (histVal.isADDOperation())
632          {
633            newHistorical.entryADDDate = csn;
634          }
635          else if (histVal.isMODDNOperation())
636          {
637            newHistorical.entryMODDNDate = csn;
638          }
639          else
640          {
641            AttributeType attrType = histVal.getAttrType();
642            if (attrType == null)
643            {
644              /*
645               * This attribute is unknown from the schema
646               * Just skip it, the modification will be processed but no
647               * historical information is going to be kept.
648               * Log information for the repair tool.
649               */
650              logger.error(ERR_UNKNOWN_ATTRIBUTE_IN_HISTORICAL, entry.getName(), histVal.getAttrString());
651              continue;
652            }
653
654            /* if attribute type does not match we create new
655             *   AttrInfoWithOptions and AttrInfo
656             *   we also add old AttrInfoWithOptions into histObj.attributesInfo
657             * if attribute type match but options does not match we create new
658             *   AttrInfo that we add to AttrInfoWithOptions
659             * if both match we keep everything
660             */
661            Set<String> options = histVal.getOptions();
662            if (attrType != lastAttrType)
663            {
664              attrInfo = AttrHistorical.createAttributeHistorical(attrType);
665
666              // Create attrInfoWithOptions and store inside the attrInfo
667              attrInfoWithOptions = new AttrHistoricalWithOptions();
668              attrInfoWithOptions.put(options, attrInfo);
669
670              // Store this attrInfoWithOptions in the newHistorical object
671              newHistorical.attributesHistorical.put(attrType, attrInfoWithOptions);
672
673              lastAttrType = attrType;
674              lastOptions = options;
675            }
676            else if (!options.equals(lastOptions))
677            {
678              attrInfo = AttrHistorical.createAttributeHistorical(attrType);
679              attrInfoWithOptions.put(options, attrInfo);
680              lastOptions = options;
681            }
682
683            attrInfo.assign(histVal.getHistKey(), histVal.getAttributeValue(), csn);
684          }
685        }
686      }
687    } catch (Exception e)
688    {
689      // Any exception happening here means that the coding of the historical
690      // information was wrong.
691      // Log an error and continue with an empty historical.
692      logger.error(ERR_BAD_HISTORICAL, entry.getName());
693    }
694
695    /* set the reference to the historical information in the entry */
696    return newHistorical;
697  }
698
699  /**
700   * Use this historical information to generate fake operations that would
701   * result in this historical information.
702   * TODO : This is only implemented for MODIFY, MODRDN and ADD
703   *        need to complete with DELETE.
704   * @param entry The Entry to use to generate the FakeOperation Iterable.
705   *
706   * @return an Iterable of FakeOperation that would result in this historical information.
707   */
708  public static Iterable<FakeOperation> generateFakeOperations(Entry entry)
709  {
710    TreeMap<CSN, FakeOperation> operations = new TreeMap<>();
711    List<Attribute> attrs = getHistoricalAttr(entry);
712    if (attrs != null)
713    {
714      for (Attribute attr : attrs)
715      {
716        for (ByteString val : attr)
717        {
718          HistoricalAttributeValue histVal = new HistoricalAttributeValue(val.toString());
719          if (histVal.isADDOperation())
720          {
721            // Found some historical information indicating that this entry was just added.
722            // Create the corresponding ADD operation.
723            operations.put(histVal.getCSN(), new FakeAddOperation(histVal.getCSN(), entry));
724          }
725          else if (histVal.isMODDNOperation())
726          {
727            // Found some historical information indicating that this entry was just renamed.
728            // Create the corresponding ADD operation.
729            operations.put(histVal.getCSN(), new FakeModdnOperation(histVal.getCSN(), entry));
730          }
731          else
732          {
733            // Found some historical information for modify operation.
734            // Generate the corresponding ModifyOperation or update
735            // the already generated Operation if it can be found.
736            CSN csn = histVal.getCSN();
737            Modification mod = histVal.generateMod();
738            FakeOperation fakeOperation = operations.get(csn);
739
740            if (fakeOperation instanceof FakeModifyOperation)
741            {
742              FakeModifyOperation modifyFakeOperation = (FakeModifyOperation) fakeOperation;
743              modifyFakeOperation.addModification(mod);
744            }
745            else
746            {
747              String uuidString = getEntryUUID(entry);
748              FakeModifyOperation modifyFakeOperation =
749                  new FakeModifyOperation(entry.getName(), csn, uuidString);
750              modifyFakeOperation.addModification(mod);
751              operations.put(histVal.getCSN(), modifyFakeOperation);
752            }
753          }
754        }
755      }
756    }
757    return operations.values();
758  }
759
760  /**
761   * Get the attribute used to store the historical information from the provided Entry.
762   *
763   * @param   entry  The entry containing the historical information.
764   * @return  The Attribute used to store the historical information.
765   *          Several values on the list if several options for this attribute.
766   *          Null if not present.
767   */
768  public static List<Attribute> getHistoricalAttr(Entry entry)
769  {
770    return entry.getAttribute(HISTORICAL_ATTRIBUTE_NAME);
771  }
772
773  /**
774   * Get the entry unique Id in String form.
775   *
776   * @param entry The entry for which the unique id should be returned.
777   * @return The Unique Id of the entry, or a fake one if none is found.
778   */
779  public static String getEntryUUID(Entry entry)
780  {
781    AttributeType attrType = DirectoryServer.getAttributeType(ENTRYUUID_ATTRIBUTE_NAME);
782    List<Attribute> uuidAttrs = entry.getOperationalAttribute(attrType);
783    return extractEntryUUID(uuidAttrs, entry.getName());
784  }
785
786  /**
787   * Get the Entry Unique Id from an add operation.
788   * This must be called after the entry uuid pre-op plugin (i.e no
789   * sooner than the replication provider pre-op)
790   *
791   * @param op The operation
792   * @return The Entry Unique Id String form.
793   */
794  public static String getEntryUUID(PreOperationAddOperation op)
795  {
796    AttributeType attrType = DirectoryServer.getAttributeType(ENTRYUUID_ATTRIBUTE_NAME);
797    List<Attribute> uuidAttrs = op.getOperationalAttributes().get(attrType);
798    return extractEntryUUID(uuidAttrs, op.getEntryDN());
799  }
800
801  /**
802   * Check if a given attribute is an attribute used to store historical
803   * information.
804   *
805   * @param   attr The attribute that needs to be checked.
806   *
807   * @return  a boolean indicating if the given attribute is
808   *          used to store historical information.
809   */
810  public static boolean isHistoricalAttribute(Attribute attr)
811  {
812    AttributeType attrType = attr.getAttributeType();
813    return HISTORICAL_ATTRIBUTE_NAME.equals(attrType.getNameOrOID());
814  }
815
816  /**
817   * Potentially update the oldest CSN stored in this entry historical
818   * with the provided CSN when its older than the current oldest.
819   *
820   * @param csn the provided CSN.
821   */
822  private void updateOldestCSN(CSN csn)
823  {
824    if (csn != null
825        && (this.oldestCSN == null || csn.isOlderThan(this.oldestCSN)))
826    {
827      this.oldestCSN = csn;
828    }
829  }
830
831  /**
832   * Returns the oldest CSN stored in this entry historical attribute.
833   *
834   * @return the oldest CSN stored in this entry historical attribute.
835   *         Returns null when this historical object has been created from
836   *         an entry that has no historical attribute and after the last
837   *         historical has been purged.
838   */
839  public CSN getOldestCSN()
840  {
841    return this.oldestCSN;
842  }
843
844  /**
845   * Extracts the entryUUID attribute value from the provided list of
846   * attributes. If the attribute is not present one is generated from the DN
847   * using the same algorithm as the entryUUID virtual attribute provider.
848   */
849  private static String extractEntryUUID(List<Attribute> entryUUIDAttributes, DN entryDN)
850  {
851    if (entryUUIDAttributes != null)
852    {
853      Attribute uuidAttr = entryUUIDAttributes.get(0);
854      if (!uuidAttr.isEmpty())
855      {
856        return uuidAttr.iterator().next().toString();
857      }
858    }
859
860    // Generate a fake entryUUID: see OPENDJ-181. In rare pathological cases
861    // an entryUUID attribute may not be present and this causes severe side effects
862    // for replication which requires the attribute to always be present
863    if (logger.isTraceEnabled())
864    {
865      logger.trace(
866          "Replication requires an entryUUID attribute in order "
867              + "to perform conflict resolution, but none was "
868              + "found in entry \"%s\": generating virtual entryUUID instead",
869          entryDN);
870    }
871
872    return UUID.nameUUIDFromBytes(entryDN.toNormalizedByteString().toByteArray()).toString();
873  }
874}