001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.replication.plugin;
018
019import java.util.Iterator;
020import java.util.LinkedHashMap;
021import java.util.Map;
022import java.util.Set;
023
024import org.forgerock.opendj.ldap.ByteString;
025import org.forgerock.opendj.ldap.ModificationType;
026import org.opends.server.replication.common.CSN;
027import org.opends.server.types.Attribute;
028import org.opends.server.types.AttributeBuilder;
029import org.opends.server.types.Entry;
030import org.opends.server.types.Modification;
031import org.forgerock.opendj.ldap.schema.AttributeType;
032
033/**
034 * This class is used to store historical information for multiple valued attributes.
035 * One object of this type is created for each attribute that was changed in the entry.
036 * It allows to record the last time a given value was added,the last
037 * time a given value was deleted and the last time the whole attribute was deleted.
038 */
039public class AttrHistoricalMultiple extends AttrHistorical
040{
041  /** Last time when the attribute was deleted. */
042  private CSN deleteTime;
043  /** Last time the attribute was modified. */
044  private CSN lastUpdateTime;
045  /**
046   * Change history for the values of this attribute.
047   * <p>
048   * We are using a LinkedHashMap here because we want:
049   * <ol>
050   * <li>Fast access for removing/adding a AttrValueHistorical keyed by the attribute value => Use a Map</li>
051   * <li>Ordering changes according to the CSN of each changes => Use a LinkedHashMap</li>
052   * </ol>
053   */
054  private final Map<AttrValueHistorical, AttrValueHistorical> valuesHist = new LinkedHashMap<>();
055
056   /**
057    * Create a new object from the provided information.
058    * @param deleteTime the last time this attribute was deleted
059    * @param updateTime the last time this attribute was updated
060    * @param valuesHist the new attribute values when updated.
061    */
062   public AttrHistoricalMultiple(CSN deleteTime,
063       CSN updateTime,
064       Map<AttrValueHistorical,AttrValueHistorical> valuesHist)
065   {
066     this.deleteTime = deleteTime;
067     this.lastUpdateTime = updateTime;
068     if (valuesHist != null)
069     {
070       this.valuesHist.putAll(valuesHist);
071     }
072   }
073
074   /** Creates a new object. */
075   public AttrHistoricalMultiple()
076   {
077     this.deleteTime = null;
078     this.lastUpdateTime = null;
079   }
080
081   /**
082    * Returns the last time when the attribute was updated.
083    * @return the last time when the attribute was updated
084    */
085   private CSN getLastUpdateTime()
086   {
087     return lastUpdateTime;
088   }
089
090   @Override
091   public CSN getDeleteTime()
092   {
093     return deleteTime;
094   }
095
096   /**
097    * Duplicate an object. CSNs are duplicated by references.
098    * <p>
099    * Method only called in tests
100    *
101    * @return the duplicated object.
102    */
103   AttrHistoricalMultiple duplicate()
104   {
105     return new AttrHistoricalMultiple(this.deleteTime, this.lastUpdateTime, this.valuesHist);
106   }
107
108   /**
109    * Delete all historical information that is older than the provided CSN for
110    * this attribute type.
111    * Add the delete attribute state information
112    * @param csn time when the delete was done
113    */
114   void delete(CSN csn)
115   {
116     // iterate through the values in the valuesInfo and suppress all the values
117     // that have not been added after the date of this delete.
118     Iterator<AttrValueHistorical> it = valuesHist.keySet().iterator();
119     while (it.hasNext())
120     {
121       AttrValueHistorical info = it.next();
122       if (csn.isNewerThanOrEqualTo(info.getValueUpdateTime()) &&
123           csn.isNewerThanOrEqualTo(info.getValueDeleteTime()))
124      {
125        it.remove();
126      }
127     }
128
129     if (csn.isNewerThan(deleteTime))
130     {
131       deleteTime = csn;
132     }
133
134     if (csn.isNewerThan(lastUpdateTime))
135     {
136       lastUpdateTime = csn;
137     }
138   }
139
140  /**
141   * Update the historical of this attribute after deleting a set of values.
142   *
143   * @param attr
144   *          the attribute containing the set of values that were deleted
145   * @param csn
146   *          time when the delete was done
147   */
148  void delete(Attribute attr, CSN csn)
149  {
150    for (ByteString val : attr)
151    {
152      delete(val, csn);
153    }
154  }
155
156   /**
157    * Update the historical of this attribute after a delete value.
158    *
159    * @param val value that was deleted
160    * @param csn time when the delete was done
161    */
162   void delete(ByteString val, CSN csn)
163   {
164     update(csn, new AttrValueHistorical(val, null, csn));
165   }
166
167  /**
168   * Update the historical information when values are added.
169   *
170   * @param attr
171   *          the attribute containing the set of added values
172   * @param csn
173   *          time when the add is done
174   */
175  private void add(Attribute attr, CSN csn)
176  {
177    for (ByteString val : attr)
178    {
179      add(val, csn);
180    }
181  }
182
183   /**
184     * Update the historical information when a value is added.
185     *
186     * @param addedValue
187     *          values that was added
188     * @param csn
189     *          time when the value was added
190     */
191   void add(ByteString addedValue, CSN csn)
192   {
193     update(csn, new AttrValueHistorical(addedValue, csn, null));
194   }
195
196  private void update(CSN csn, AttrValueHistorical valInfo)
197  {
198    updateValInfo(valInfo, valInfo);
199    if (csn.isNewerThan(lastUpdateTime))
200    {
201      lastUpdateTime = csn;
202    }
203  }
204
205  private void updateValInfo(AttrValueHistorical oldValInfo, AttrValueHistorical newValInfo)
206  {
207    valuesHist.remove(oldValInfo);
208    valuesHist.put(newValInfo, newValInfo);
209  }
210
211  @Override
212  public Set<AttrValueHistorical> getValuesHistorical()
213  {
214    return valuesHist.keySet();
215  }
216
217  @Override
218  public boolean replayOperation(Iterator<Modification> modsIterator, CSN csn,
219      Entry modifiedEntry, Modification m)
220  {
221    if (csn.isNewerThanOrEqualTo(getLastUpdateTime())
222        && m.getModificationType() == ModificationType.REPLACE)
223    {
224      processLocalOrNonConflictModification(csn, m);
225      return false;// the attribute was not modified more recently
226    }
227    // We are replaying an operation that was already done
228    // on another master server and this operation has a potential
229    // conflict with some more recent operations on this same entry
230    // we need to take the more complex path to solve them
231    return replayPotentialConflictModification(modsIterator, csn, modifiedEntry, m);
232  }
233
234  private boolean replayPotentialConflictModification(Iterator<Modification> modsIterator, CSN csn,
235      Entry modifiedEntry, Modification m)
236  {
237    // the attribute was modified after this change -> conflict
238    switch (m.getModificationType().asEnum())
239    {
240    case DELETE:
241      if (csn.isOlderThan(getDeleteTime()))
242      {
243        /* this delete is already obsoleted by a more recent delete
244         * skip this mod
245         */
246        modsIterator.remove();
247        return true;
248      }
249
250      if (!processDeleteConflict(csn, m, modifiedEntry))
251      {
252        modsIterator.remove();
253        return true;
254      }
255      return false;
256
257    case ADD:
258      if (!processAddConflict(csn, m))
259      {
260        modsIterator.remove();
261        return true;
262      }
263      return false;
264
265    case REPLACE:
266      if (csn.isOlderThan(getDeleteTime()))
267      {
268        /* this replace is already obsoleted by a more recent delete
269         * skip this mod
270         */
271        modsIterator.remove();
272        return true;
273      }
274
275      /* save the values that are added by the replace operation into addedValues
276       * first process the replace as a delete operation
277       * -> this generates a list of values that should be kept
278       * then process the addedValues as if they were coming from an add
279       * -> this generates the list of values that needs to be added
280       * concatenate the 2 generated lists into a replace
281       */
282      boolean conflict = false;
283      Attribute addedValues = m.getAttribute();
284      m.setAttribute(new AttributeBuilder(addedValues, true).toAttribute());
285
286      processDeleteConflict(csn, m, modifiedEntry);
287      Attribute keptValues = m.getAttribute();
288
289      m.setAttribute(addedValues);
290      if (!processAddConflict(csn, m))
291      {
292        modsIterator.remove();
293        conflict = true;
294      }
295
296      AttributeBuilder builder = new AttributeBuilder(keptValues);
297      builder.addAll(m.getAttribute());
298      m.setAttribute(builder.toAttribute());
299      return conflict;
300
301    case INCREMENT:
302      // TODO : FILL ME
303      return false;
304
305    default:
306      return false;
307    }
308  }
309
310  @Override
311  public void processLocalOrNonConflictModification(CSN csn, Modification mod)
312  {
313    /*
314     * The operation is either a non-conflicting operation or a local operation
315     * so there is no need to check the historical information for conflicts.
316     * If this is a local operation, then this code is run after
317     * the pre-operation phase.
318     * If this is a non-conflicting replicated operation, this code is run
319     * during the handleConflictResolution().
320     */
321
322    Attribute modAttr = mod.getAttribute();
323    AttributeType type = modAttr.getAttributeDescription().getAttributeType();
324
325    switch (mod.getModificationType().asEnum())
326    {
327    case DELETE:
328      if (modAttr.isEmpty())
329      {
330        delete(csn);
331      }
332      else
333      {
334        delete(modAttr, csn);
335      }
336      break;
337
338    case ADD:
339      if (type.isSingleValue())
340      {
341        delete(csn);
342      }
343      add(modAttr, csn);
344      break;
345
346    case REPLACE:
347      /* TODO : can we replace specific attribute values ????? */
348      delete(csn);
349      add(modAttr, csn);
350      break;
351
352    case INCREMENT:
353      /* FIXME : we should update CSN */
354      break;
355    }
356  }
357
358  /**
359   * Process a delete attribute values that is conflicting with a previous modification.
360   *
361   * @param csn The CSN of the currently processed change
362   * @param m the modification that is being processed
363   * @param modifiedEntry the entry that is modified (before current mod)
364   * @return {@code true} if no conflict was detected, {@code false} otherwise.
365   */
366  private boolean processDeleteConflict(CSN csn, Modification m, Entry modifiedEntry)
367  {
368    /*
369     * We are processing a conflicting DELETE modification
370     *
371     * This code is written on the assumption that conflict are
372     * rare. We therefore don't care much about the performance
373     * However since it is rarely executed this code needs to be
374     * as simple as possible to make sure that all paths are tested.
375     * In this case the most simple seem to change the DELETE
376     * in a REPLACE modification that keeps all values
377     * more recent that the DELETE.
378     * we are therefore going to change m into a REPLACE that will keep
379     * all the values that have been updated after the DELETE time
380     * If a value is present in the entry without any state information
381     * it must be removed so we simply ignore them
382     */
383
384    Attribute modAttr = m.getAttribute();
385    if (modAttr.isEmpty())
386    {
387      // We are processing a DELETE attribute modification
388      m.setModificationType(ModificationType.REPLACE);
389      AttributeBuilder builder = new AttributeBuilder(modAttr, true);
390
391      for (Iterator<AttrValueHistorical> it = valuesHist.keySet().iterator(); it.hasNext();)
392      {
393        AttrValueHistorical valInfo = it.next();
394
395        if (csn.isOlderThan(valInfo.getValueUpdateTime()))
396        {
397          // this value has been updated after this delete,
398          // therefore this value must be kept
399          builder.add(valInfo.getAttributeValue());
400        }
401        else if (csn.isNewerThanOrEqualTo(valInfo.getValueDeleteTime()))
402        {
403          /*
404           * this value is going to be deleted, remove it from historical
405           * information unless it is a Deleted attribute value that is
406           * more recent than this DELETE
407           */
408          it.remove();
409        }
410      }
411
412      m.setAttribute(builder.toAttribute());
413
414      if (csn.isNewerThan(getDeleteTime()))
415      {
416        deleteTime = csn;
417      }
418      if (csn.isNewerThan(getLastUpdateTime()))
419      {
420        lastUpdateTime = csn;
421      }
422    }
423    else
424    {
425      // we are processing DELETE of some attribute values
426      AttributeBuilder builder = new AttributeBuilder(modAttr);
427
428      for (ByteString val : modAttr)
429      {
430        boolean deleteIt = true;  // true if the delete must be done
431        boolean addedInCurrentOp = false;
432
433        // update historical information
434        AttrValueHistorical valInfo = new AttrValueHistorical(val, null, csn);
435        AttrValueHistorical oldValInfo = valuesHist.get(valInfo);
436        if (oldValInfo != null)
437        {
438          // this value already exist in the historical information
439          if (csn.equals(oldValInfo.getValueUpdateTime()))
440          {
441            // This value was added earlier in the same operation
442            // we need to keep the delete.
443            addedInCurrentOp = true;
444          }
445          if (csn.isNewerThanOrEqualTo(oldValInfo.getValueDeleteTime()) &&
446              csn.isNewerThanOrEqualTo(oldValInfo.getValueUpdateTime()))
447          {
448            updateValInfo(oldValInfo, valInfo);
449          }
450          else if (oldValInfo.isUpdate())
451          {
452            deleteIt = false;
453          }
454        }
455        else
456        {
457          updateValInfo(oldValInfo, valInfo);
458        }
459
460        /* if the attribute value is not to be deleted
461         * or if attribute value is not present suppress it from the
462         * MOD to make sure the delete is going to succeed
463         */
464        if (!deleteIt
465            || (!modifiedEntry.hasValue(modAttr.getAttributeDescription(), val) && ! addedInCurrentOp))
466        {
467          // this value was already deleted before and therefore
468          // this should not be replayed.
469          builder.remove(val);
470          if (builder.isEmpty())
471          {
472            // This was the last values in the set of values to be deleted.
473            // this MOD must therefore be skipped.
474            return false;
475          }
476        }
477      }
478
479      m.setAttribute(builder.toAttribute());
480
481      if (csn.isNewerThan(getLastUpdateTime()))
482      {
483        lastUpdateTime = csn;
484      }
485    }
486
487    return true;
488  }
489
490  /**
491   * Process a add attribute values that is conflicting with a previous modification.
492   *
493   * @param csn
494   *          the historical info associated to the entry
495   * @param m
496   *          the modification that is being processed
497   * @return {@code true} if no conflict was detected, {@code false} otherwise.
498   */
499  private boolean processAddConflict(CSN csn, Modification m)
500  {
501    /*
502     * if historicalattributedelete is newer forget this mod else find
503     * attr value if does not exist add historicalvalueadded timestamp
504     * add real value in entry else if timestamp older and already was
505     * historicalvalueadded update historicalvalueadded else if
506     * timestamp older and was historicalvaluedeleted change
507     * historicalvaluedeleted into historicalvalueadded add value in
508     * real entry
509     */
510
511    if (csn.isOlderThan(getDeleteTime()))
512    {
513      /* A delete has been done more recently than this add
514       * forget this MOD ADD
515       */
516      return false;
517    }
518
519    AttributeBuilder builder = new AttributeBuilder(m.getAttribute());
520    for (ByteString addVal : m.getAttribute())
521    {
522      AttrValueHistorical valInfo = new AttrValueHistorical(addVal, csn, null);
523      AttrValueHistorical oldValInfo = valuesHist.get(valInfo);
524      if (oldValInfo == null)
525      {
526        /* this value does not exist yet
527         * add it in the historical information
528         * let the operation process normally
529         */
530        valuesHist.put(valInfo, valInfo);
531      }
532      else
533      {
534        if  (oldValInfo.isUpdate())
535        {
536          /* if the value is already present
537           * check if the updateTime must be updated
538           * in all cases suppress this value from the value list
539           * as it is already present in the entry
540           */
541          if (csn.isNewerThan(oldValInfo.getValueUpdateTime()))
542          {
543            updateValInfo(oldValInfo, valInfo);
544          }
545          builder.remove(addVal);
546        }
547        else
548        { // it is a delete
549          /* this value is marked as a deleted value
550           * check if this mod is more recent the this delete
551           */
552          if (csn.isNewerThanOrEqualTo(oldValInfo.getValueDeleteTime()))
553          {
554            updateValInfo(oldValInfo, valInfo);
555          }
556          else
557          {
558            /* the delete that is present in the historical information
559             * is more recent so it must win,
560             * remove this value from the list of values to add
561             * don't update the historical information
562             */
563            builder.remove(addVal);
564          }
565        }
566      }
567    }
568
569    Attribute attr = builder.toAttribute();
570    m.setAttribute(attr);
571
572    if (attr.isEmpty())
573    {
574      return false;
575    }
576
577    if (csn.isNewerThan(getLastUpdateTime()))
578    {
579      lastUpdateTime = csn;
580    }
581    return true;
582  }
583
584  @Override
585  public void assign(HistAttrModificationKey histKey, ByteString value, CSN csn)
586  {
587    switch (histKey)
588    {
589    case ADD:
590      if (value != null)
591      {
592        add(value, csn);
593      }
594      break;
595
596    case DEL:
597      if (value != null)
598      {
599        delete(value, csn);
600      }
601      break;
602
603    case REPL:
604      delete(csn);
605      if (value != null)
606      {
607        add(value, csn);
608      }
609      break;
610
611    case ATTRDEL:
612      delete(csn);
613      break;
614    }
615  }
616
617  @Override
618  public String toString()
619  {
620    final StringBuilder sb = new StringBuilder();
621    sb.append(getClass().getSimpleName()).append("(");
622    boolean deleteAppended = false;
623    if (deleteTime != null)
624    {
625      deleteAppended = true;
626      sb.append("deleteTime=").append(deleteTime);
627    }
628    if (lastUpdateTime != null)
629    {
630      if (deleteAppended)
631      {
632        sb.append(", ");
633      }
634      sb.append("lastUpdateTime=").append(lastUpdateTime);
635    }
636    sb.append(", valuesHist=").append(valuesHist.keySet());
637    sb.append(")");
638    return sb.toString();
639  }
640}