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 2008-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.replication.plugin;
028
029import static org.opends.server.replication.plugin.HistAttrModificationKey.*;
030
031import java.util.Collections;
032import java.util.Iterator;
033import java.util.Set;
034
035import org.forgerock.opendj.ldap.ByteString;
036import org.forgerock.opendj.ldap.ModificationType;
037import org.opends.server.replication.common.CSN;
038import org.opends.server.types.Attribute;
039import org.opends.server.types.AttributeType;
040import org.opends.server.types.Entry;
041import org.opends.server.types.Modification;
042
043/**
044 * This class is used to store historical information for single valued attributes.
045 * One object of this type is created for each attribute that was changed in the entry.
046 * It allows to record the last time a given value was added,
047 * and the last time the whole attribute was deleted.
048 */
049public class AttrHistoricalSingle extends AttrHistorical
050{
051  /** Last time when the attribute was deleted. */
052  private CSN deleteTime;
053  /** Last time when a value was added. */
054  private CSN addTime;
055  /** Last added value. */
056  private ByteString value;
057  /**
058   * Last operation applied. This is only used for multiple mods on the same
059   * single valued attribute in the same modification.
060   */
061  private HistAttrModificationKey lastMod;
062
063  @Override
064  public CSN getDeleteTime()
065  {
066    return this.deleteTime;
067  }
068
069  @Override
070  public Set<AttrValueHistorical> getValuesHistorical()
071  {
072    if (addTime != null)
073    {
074      return Collections.singleton(new AttrValueHistorical(value, addTime, null));
075    }
076    return Collections.emptySet();
077  }
078
079  @Override
080  public void processLocalOrNonConflictModification(CSN csn, Modification mod)
081  {
082    Attribute modAttr = mod.getAttribute();
083    ByteString newValue = getSingleValue(modAttr);
084
085    switch (mod.getModificationType().asEnum())
086    {
087    case DELETE:
088      delete(csn, newValue);
089      break;
090
091    case ADD:
092      add(csn, newValue);
093      break;
094
095    case REPLACE:
096      replaceOrDelete(csn, newValue);
097      break;
098
099    case INCREMENT:
100      /* FIXME : we should update CSN */
101      break;
102    }
103  }
104
105  private void replaceOrDelete(CSN csn, ByteString newValue)
106  {
107    if (newValue != null)
108    {
109      replace(csn, newValue);
110    }
111    else
112    {
113      delete(csn, null);
114    }
115  }
116
117  private void add(CSN csn, ByteString newValue)
118  {
119    addTime = csn;
120    value = newValue;
121    lastMod = ADD;
122  }
123
124  private void replace(CSN csn, ByteString newValue)
125  {
126    addTime = csn;
127    deleteTime = csn;
128    value = newValue;
129    lastMod = REPL;
130  }
131
132  private void delete(CSN csn, ByteString newValue)
133  {
134    addTime = null;
135    deleteTime = csn;
136    value = newValue;
137    lastMod = DEL;
138  }
139
140  private void deleteWithoutDeleteTime()
141  {
142    addTime = null;
143    value = null;
144    lastMod = DEL;
145  }
146
147  @Override
148  public boolean replayOperation(Iterator<Modification> modsIterator, CSN csn,
149      Entry modifiedEntry, Modification mod)
150  {
151    Attribute modAttr = mod.getAttribute();
152    ByteString newValue = getSingleValue(modAttr);
153
154    boolean conflict = false;
155    switch (mod.getModificationType().asEnum())
156    {
157    case DELETE:
158      if (csn.isNewerThan(addTime))
159      {
160        if (newValue == null || newValue.equals(value) || value == null)
161        {
162          if (csn.isNewerThan(deleteTime))
163          {
164            deleteTime = csn;
165          }
166          AttributeType type = modAttr.getAttributeType();
167          if (!modifiedEntry.hasAttribute(type))
168          {
169            conflict = true;
170            modsIterator.remove();
171          }
172          else if (newValue != null &&
173              !modifiedEntry.hasValue(type, modAttr.getOptions(), newValue))
174          {
175            conflict = true;
176            modsIterator.remove();
177          }
178          else
179          {
180            deleteWithoutDeleteTime();
181          }
182        }
183        else
184        {
185          conflict = true;
186          modsIterator.remove();
187        }
188      }
189      else if (csn.equals(addTime))
190      {
191        if (lastMod == ADD || lastMod == REPL)
192        {
193          if (csn.isNewerThan(deleteTime))
194          {
195            deleteTime = csn;
196          }
197          deleteWithoutDeleteTime();
198        }
199        else
200        {
201          conflict = true;
202          modsIterator.remove();
203        }
204      }
205      else
206      {
207          conflict = true;
208          modsIterator.remove();
209      }
210      break;
211
212    case ADD:
213      if (csn.isNewerThanOrEqualTo(deleteTime) && csn.isOlderThan(addTime))
214      {
215        conflict = true;
216        mod.setModificationType(ModificationType.REPLACE);
217        addTime = csn;
218        value = newValue;
219        lastMod = REPL;
220      }
221      else
222      {
223        if (csn.isNewerThanOrEqualTo(deleteTime)
224            && (addTime == null || addTime.isOlderThan(deleteTime)))
225        {
226          add(csn, newValue);
227        }
228        else
229        {
230          // Case where CSN = addTime = deleteTime
231          if (csn.equals(deleteTime) && csn.equals(addTime)
232              && lastMod == DEL)
233          {
234            add(csn, newValue);
235          }
236          else
237          {
238            conflict = true;
239            modsIterator.remove();
240          }
241        }
242      }
243
244      break;
245
246    case REPLACE:
247      if (csn.isOlderThan(deleteTime))
248      {
249        conflict = true;
250        modsIterator.remove();
251      }
252      else
253      {
254        replaceOrDelete(csn, newValue);
255      }
256      break;
257
258    case INCREMENT:
259      /* FIXME : we should update CSN */
260      break;
261    }
262    return conflict;
263  }
264
265  private ByteString getSingleValue(Attribute modAttr)
266  {
267    if (modAttr != null && !modAttr.isEmpty())
268    {
269      return modAttr.iterator().next();
270    }
271    return null;
272  }
273
274  @Override
275  public void assign(HistAttrModificationKey histKey, ByteString value, CSN csn)
276  {
277    switch (histKey)
278    {
279    case ADD:
280      this.addTime = csn;
281      this.value = value;
282      break;
283
284    case DEL:
285      this.deleteTime = csn;
286      if (value != null)
287      {
288        this.value = value;
289      }
290      break;
291
292    case REPL:
293      this.addTime = this.deleteTime = csn;
294      if (value != null)
295      {
296        this.value = value;
297      }
298      break;
299
300    case ATTRDEL:
301      this.deleteTime = csn;
302      break;
303    }
304  }
305
306  @Override
307  public String toString()
308  {
309    final StringBuilder sb = new StringBuilder();
310    if (deleteTime != null)
311    {
312      sb.append("deleteTime=").append(deleteTime);
313    }
314    if (addTime != null)
315    {
316      if (sb.length() > 0)
317      {
318        sb.append(", ");
319      }
320      sb.append("addTime=").append(addTime);
321    }
322    if (sb.length() > 0)
323    {
324      sb.append(", ");
325    }
326    sb.append("value=").append(value)
327      .append(", lastMod=").append(lastMod);
328    return getClass().getSimpleName() + "(" + sb + ")";
329  }
330}